Skip to content
This repository was archived by the owner on Apr 21, 2021. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 185 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# Django running on Zeit Now


## Tutorial


Expand Down Expand Up @@ -152,4 +151,188 @@ $ now
> Success! Deployment ready [57s]
```

Check your results by visiting https://zeit.co/dashboard/project/python-wsgi-example
Check your results by visiting https://zeit.co/dashboard/project/now-django-example

## Ready to add a database? Try Postgres.

### Configure Django Settings

Configure `now_app/settings.py` to accept database connection strings using `dj_database_url`

```
# now_app/settings.py

import dj_database_url

DATABASES = {'default': dj_database_url.config(conn_max_age=600)}

```

While in `now_app/settings.py`, add `whitenoise`. WhiteNoiseMiddleware enables Django to serve
its own static files. Without whitenoise, Django Admin won't import necessary CSS, and we will use
the Django admin to test whether or not our database connection is working.

```
# now_app/settings.py

MIDDLEWARE = [
... other stuff ...,
'whitenoise.middleware.WhiteNoiseMiddleware',
]
```

Add both `whitenoise` and `dj_database_url` to requirements.txt

At this point, requirements.txt file should include:

```
# requirements.txt

dj-database-url==0.5.0
Django==2.1.7
Werkzeug >=0.14,<1
whitenoise
```
### Add a pre-compiled Django Postgres adapter: psycopg2

Django needs an adapter like `psycopg2` to connect to Postgres.

Only certain libraries are available in the lambda environment, and `psycopg2` is not
one of them... so simply adding `psycopg2` to `requirements.txt` will not work.

Instead, bundle a pre-compiled `psycopg2` with your project at root level, where Django can import
as needed. `psycopg2` must be compiled for the correct python version and environment.

Fortunately, [@jkehler](https://github.com/jkehler) has done the work for us in this repo:
[https://github.com/jkehler/awslambda-psycopg2](https://github.com/jkehler/awslambda-psycopg2)

Download package `awslambda-psychopg2` as a zip file, and copy this subfolder into your
project's root directory:

`awslambda-psychopg2-master/with_ssl_support/psychopg2-3.6`

Rename folder `psychopg2-3.6` to `psychopg2`

Project directory should look like this:
```
/now-django-example
/example
/now_app
/psycopg2 # this one you just added.
index.py
manage.py
now.json
README.md
requirements.txt
```

### Create a database server

One easy way to get up and running fast is to use a Digital Ocean Database Cluster.

- Log in at digitalocean.com
- Select the green 'Create' button in the top right > Databases
- Follow instructions to create a PostgreSQL 10 server

Feel free to skip the "getting started" section.

On the Connection Details panel, select dropdown "Connection String" and Copy.

Your connection string should look something like this:

```
postgres://doadmin:<YOUR-PASSWORD>@<YOUR-DATABASE-SERVER>.db.ondigitalocean.com:25060/defaultdb?sslmode=require
```


_Definitely don't use this default connection string in prod. For prod, create database users with specific permissions. We only use this connection string because it is easy for a tutorial / proof-of-concept_.

### Seed the database server with a user

Lambda environments are immutable, and you cannot run environment commands like `python manage.py ...`

One way to seed the database is to run Django commands locally, _before_ deploying to Now (though connecting to the same database server).

In your environment, set environment variable DATABASE_URL to the database connection string from Digital Ocean.

```console
user:~/now-django-example $ export DATABASE_URL=postgres://doadmin:<YOUR-PASSWORD>@<YOUR-DATABASE-SERVER>.db.ondigitalocean.com:25060/defaultdb?sslmode=require

user:~/now-django-example $ echo $DATABASE_URL
postgres://doadmin:<YOUR-PASSWORD>@<YOUR-DATABASE-SERVER>.db.ondigitalocean.com:25060/defaultdb?sslmode=require
```

Ensure your local environment has installed requirements.txt
```console
user:~/now-django-example $ python3 -m pip install -r requirements.txt
```

Then use manage.py to create a superuser:
```console
user:~/now-django-example $ python3 manage.py createsuperuser
```

Follow Django's instructions to create the super user.

Run the server and try logging in to make sure the database is seeded correctly.

```console
user:~/now-django-example $ python3 manage.py runserver
```
Navigate to: https://localhost:8000/admin and log in using the super user you created above.

Your database is seeded. Stop the server using CTRL+C and proceed with Now deployment.

### Modify now.json environment variables and routes.

`dj_database_url` will look for an environment variable named `DATABASE_URL`.

Your `DATABASE_URL`, however, contains sensitive information, so we will use a [now secret](https://zeit.co/docs/v1/features/env-and-secrets).

```console
user:~/now-django-example $ now secret add db-url postgres://doadmin:<YOUR-PASSWORD>@<YOUR-DATABASE-SERVER>.db.ondigitalocean.com:25060/defaultdb?sslmode=require
> Success! Secret db-url added (pejowei) [466ms]
```

Add environment variable to `now.json`

```json
{
"env":{
"DATABASE_URL":"@db-url"
}
}
```

While editing `now.json`, this is a good time to update routes.

We want all requests and responses to be routed through the index.py handler file, so your Django
application needs to be routed like a single-page app.

Add the following to `now.json` to send all requests to your Django wsgi application via index.py:

```json
{
"routes" : [{
"src" : "/(.*)", "dest":"/"
}],
}
```

### Deploy

You can now redeploy (pun intended) your new application:
```
$ now
> Deploying now-django-example under pejowei
...
> Success! Deployment ready [57s]
```

Check your results by visiting https://zeit.co/dashboard/project/now-django-example

You will know if everything worked if you can log into Django admin and add a user.

Django admin will be available at https://YOUR-DEPLOYMENT.now.sh/admin

Good luck!
3 changes: 2 additions & 1 deletion example/urls.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from django.urls import path

from django.contrib import admin
from example.views import index


urlpatterns = [
path('admin/',admin.site.urls),
path('', index),
]
8 changes: 7 additions & 1 deletion now.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,11 @@
"src": "index.py",
"use": "@ardent-labs/now-python-wsgi",
"config": { "maxLambdaSize": "15mb" }
}]
}],
"routes" : [{
"src" : "/(.*)", "dest":"/"
}],
"env":{
"DATABASE_URL":"@db-url"
}
}
9 changes: 7 additions & 2 deletions now_app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

import os

import dj_database_url

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

Expand All @@ -25,7 +27,7 @@
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = ['.now.sh']
ALLOWED_HOSTS = ['*','.now.sh']


# Application definition
Expand All @@ -48,6 +50,7 @@
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
]

ROOT_URLCONF = 'now_app.urls'
Expand All @@ -74,7 +77,9 @@
# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases

DATABASES = {}
DATABASES = {
'default': dj_database_url.config(conn_max_age=600)
}


# Password validation
Expand Down
134 changes: 134 additions & 0 deletions psycopg2/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
"""A Python driver for PostgreSQL

psycopg is a PostgreSQL_ database adapter for the Python_ programming
language. This is version 2, a complete rewrite of the original code to
provide new-style classes for connection and cursor objects and other sweet
candies. Like the original, psycopg 2 was written with the aim of being very
small and fast, and stable as a rock.

Homepage: http://initd.org/projects/psycopg2

.. _PostgreSQL: http://www.postgresql.org/
.. _Python: http://www.python.org/

:Groups:
* `Connections creation`: connect
* `Value objects constructors`: Binary, Date, DateFromTicks, Time,
TimeFromTicks, Timestamp, TimestampFromTicks
"""
# psycopg/__init__.py - initialization of the psycopg module
#
# Copyright (C) 2003-2010 Federico Di Gregorio <fog@debian.org>
#
# psycopg2 is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# In addition, as a special exception, the copyright holders give
# permission to link this program with the OpenSSL library (or with
# modified versions of OpenSSL that use the same license as OpenSSL),
# and distribute linked combinations including the two.
#
# You must obey the GNU Lesser General Public License in all respects for
# all of the code used other than OpenSSL.
#
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.

# Import modules needed by _psycopg to allow tools like py2exe to do
# their work without bothering about the module dependencies.

# Note: the first internal import should be _psycopg, otherwise the real cause
# of a failed loading of the C module may get hidden, see
# http://archives.postgresql.org/psycopg/2011-02/msg00044.php

# Import the DBAPI-2.0 stuff into top-level module.

from psycopg2._psycopg import ( # noqa
BINARY, NUMBER, STRING, DATETIME, ROWID,

Binary, Date, Time, Timestamp,
DateFromTicks, TimeFromTicks, TimestampFromTicks,

Error, Warning, DataError, DatabaseError, ProgrammingError, IntegrityError,
InterfaceError, InternalError, NotSupportedError, OperationalError,

_connect, apilevel, threadsafety, paramstyle,
__version__, __libpq_version__,
)

from psycopg2 import tz # noqa


# Register default adapters.

import psycopg2.extensions as _ext
_ext.register_adapter(tuple, _ext.SQL_IN)
_ext.register_adapter(type(None), _ext.NoneAdapter)

# Register the Decimal adapter here instead of in the C layer.
# This way a new class is registered for each sub-interpreter.
# See ticket #52
try:
from decimal import Decimal
except ImportError:
pass
else:
from psycopg2._psycopg import Decimal as Adapter
_ext.register_adapter(Decimal, Adapter)
del Decimal, Adapter


def connect(dsn=None, connection_factory=None, cursor_factory=None, **kwargs):
"""
Create a new database connection.

The connection parameters can be specified as a string:

conn = psycopg2.connect("dbname=test user=postgres password=secret")

or using a set of keyword arguments:

conn = psycopg2.connect(database="test", user="postgres", password="secret")

Or as a mix of both. The basic connection parameters are:

- *dbname*: the database name
- *database*: the database name (only as keyword argument)
- *user*: user name used to authenticate
- *password*: password used to authenticate
- *host*: database host address (defaults to UNIX socket if not provided)
- *port*: connection port number (defaults to 5432 if not provided)

Using the *connection_factory* parameter a different class or connections
factory can be specified. It should be a callable object taking a dsn
argument.

Using the *cursor_factory* parameter, a new default cursor factory will be
used by cursor().

Using *async*=True an asynchronous connection will be created. *async_* is
a valid alias (for Python versions where ``async`` is a keyword).

Any other keyword parameter will be passed to the underlying client
library: the list of supported parameters depends on the library version.

"""
kwasync = {}
if 'async' in kwargs:
kwasync['async'] = kwargs.pop('async')
if 'async_' in kwargs:
kwasync['async_'] = kwargs.pop('async_')

if dsn is None and not kwargs:
raise TypeError('missing dsn and no parameters')

dsn = _ext.make_dsn(dsn, **kwargs)
conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
if cursor_factory is not None:
conn.cursor_factory = cursor_factory

return conn
Binary file added psycopg2/__pycache__/__init__.cpython-36.pyc
Binary file not shown.
Binary file added psycopg2/__pycache__/_ipaddress.cpython-36.pyc
Binary file not shown.
Binary file added psycopg2/__pycache__/_json.cpython-36.pyc
Binary file not shown.
Binary file added psycopg2/__pycache__/_range.cpython-36.pyc
Binary file not shown.
Binary file added psycopg2/__pycache__/errorcodes.cpython-36.pyc
Binary file not shown.
Binary file added psycopg2/__pycache__/extensions.cpython-36.pyc
Binary file not shown.
Binary file added psycopg2/__pycache__/extras.cpython-36.pyc
Binary file not shown.
Binary file added psycopg2/__pycache__/tz.cpython-36.pyc
Binary file not shown.
Loading