diff --git a/opentech/apply/dashboard/urls.py b/opentech/apply/dashboard/urls.py
index 26944a19f4947f652bb4bd7c9c191c796eb1f7fe..d8710de3c1d3730bf0ac5960335a8e549ba8e85f 100644
--- a/opentech/apply/dashboard/urls.py
+++ b/opentech/apply/dashboard/urls.py
@@ -1,8 +1,10 @@
-from django.conf.urls import url
+from django.urls import path
 
 from .views import DashboardView
 
 
+app_name = 'dashboard'
+
 urlpatterns = [
-    url(r'^$', DashboardView.as_view(), name="dashboard"),
+    path('', DashboardView.as_view(), name="dashboard"),
 ]
diff --git a/opentech/apply/funds/migrations/0026_django2_update.py b/opentech/apply/funds/migrations/0026_django2_update.py
new file mode 100644
index 0000000000000000000000000000000000000000..2f0241f475665f77859ec4858b927904a20a9aea
--- /dev/null
+++ b/opentech/apply/funds/migrations/0026_django2_update.py
@@ -0,0 +1,35 @@
+# Generated by Django 2.0.2 on 2018-03-01 21:46
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('funds', '0025_update_with_file_blocks'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='fundform',
+            name='form',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='funds.ApplicationForm'),
+        ),
+        migrations.AlterField(
+            model_name='labform',
+            name='form',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='funds.ApplicationForm'),
+        ),
+        migrations.AlterField(
+            model_name='round',
+            name='lead',
+            field=models.ForeignKey(limit_choices_to={'groups__name': 'Staff'}, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
+        ),
+        migrations.AlterField(
+            model_name='roundform',
+            name='form',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='funds.ApplicationForm'),
+        ),
+    ]
diff --git a/opentech/apply/funds/models.py b/opentech/apply/funds/models.py
index 82c2e6aeaf876844e2ef1bcbd2281d5615c69386..1f2eb0d1c139ce8d98c7c6ee451fb3bf67d40ed0 100644
--- a/opentech/apply/funds/models.py
+++ b/opentech/apply/funds/models.py
@@ -212,7 +212,7 @@ class FundType(EmailForm, WorkflowStreamForm):  # type: ignore
 
 
 class AbstractRelatedForm(Orderable):
-    form = models.ForeignKey('ApplicationForm')
+    form = models.ForeignKey('ApplicationForm', on_delete=models.PROTECT)
 
     panels = [
         FilteredFieldPanel('form', filter_query={'roundform__isnull': True})
@@ -260,7 +260,11 @@ class Round(WorkflowStreamForm, SubmittableStreamForm):  # type: ignore
     parent_page_types = ['funds.FundType']
     subpage_types = []  # type: ignore
 
-    lead = models.ForeignKey(settings.AUTH_USER_MODEL, limit_choices_to={'groups__name': STAFF_GROUP_NAME})
+    lead = models.ForeignKey(
+        settings.AUTH_USER_MODEL,
+        limit_choices_to={'groups__name': STAFF_GROUP_NAME},
+        on_delete=models.PROTECT,
+    )
     start_date = models.DateField(default=date.today)
     end_date = models.DateField(
         blank=True,
diff --git a/opentech/apply/funds/urls.py b/opentech/apply/funds/urls.py
index c4cbf608615ac34dcde79326ee39c68fa7be80ed..2f97c27a2d502ba10b600d55643e76a6350eaad9 100644
--- a/opentech/apply/funds/urls.py
+++ b/opentech/apply/funds/urls.py
@@ -1,11 +1,13 @@
-from django.conf.urls import url
+from django.urls import path
 
 from .views import SubmissionSearchView, SubmissionDetailView, SubmissionListView, demo_workflow
 
 
+app_name = 'funds'
+
 urlpatterns = [
-    url(r'^demo/(?P<wf_id>[1-2])/$', demo_workflow, name="workflow_demo"),
-    url(r'^submissions/$', SubmissionListView.as_view(), name="submissions"),
-    url(r'^submissions/(?P<pk>\d+)/$', SubmissionDetailView.as_view(), name="submission"),
-    url(r'^search$', SubmissionSearchView.as_view(), name="search"),
+    path('demo/<int:wf_id>/', demo_workflow, name="workflow_demo"),
+    path('submissions/', SubmissionListView.as_view(), name="submissions"),
+    path('submissions/<int:pk>/', SubmissionDetailView.as_view(), name="submission"),
+    path('search', SubmissionSearchView.as_view(), name="search"),
 ]
diff --git a/opentech/apply/urls.py b/opentech/apply/urls.py
index 6a4d437d8a11358373ae6238415d399796d40b6e..b4e15d29aa204ca776999913e155ee54aa787c53 100644
--- a/opentech/apply/urls.py
+++ b/opentech/apply/urls.py
@@ -1,4 +1,4 @@
-from django.conf.urls import include, url
+from django.urls import include, path
 
 from .funds import urls as funds_urls
 from .users import urls as users_urls
@@ -6,7 +6,7 @@ from .dashboard import urls as dashboard_urls
 
 
 urlpatterns = [
-    url(r'^apply/', include(funds_urls, namespace='funds')),
-    url(r'^account/', include(users_urls, namespace='users')),
-    url(r'^dashboard/', include(dashboard_urls, namespace='dashboard')),
+    path('apply/', include(funds_urls)),
+    path('account/', include(users_urls)),
+    path('dashboard/', include(dashboard_urls)),
 ]
diff --git a/opentech/apply/users/migrations/0004_django2_update.py b/opentech/apply/users/migrations/0004_django2_update.py
new file mode 100644
index 0000000000000000000000000000000000000000..8e03bee9ee33ae6e19638d3cd3ee5ff39f480848
--- /dev/null
+++ b/opentech/apply/users/migrations/0004_django2_update.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.0.2 on 2018-03-01 21:46
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('users', '0003_make_email_username'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='user',
+            name='last_name',
+            field=models.CharField(blank=True, max_length=150, verbose_name='last name'),
+        ),
+    ]
diff --git a/opentech/apply/users/urls.py b/opentech/apply/users/urls.py
index babfad10601e76ff1e6db1f4f1a5ba37c4db0638..315e0cfe44c3000fbb11a22d5dd5b54b8ce40f54 100644
--- a/opentech/apply/users/urls.py
+++ b/opentech/apply/users/urls.py
@@ -1,13 +1,16 @@
-from django.conf.urls import url
+from django.urls import path
 from django.contrib.auth import views as auth_views
 from django.urls import reverse_lazy
 
 from opentech.apply.users.views import account, oauth, ActivationView, create_password
 
+
+app_name = 'users'
+
 urlpatterns = [
-    url(r'^$', account, name='account'),
-    url(
-        r'^login/$',
+    path('', account, name='account'),
+    path(
+        'login/',
         auth_views.LoginView.as_view(
             template_name='users/login.html',
             redirect_authenticated_user=True
@@ -16,11 +19,11 @@ urlpatterns = [
     ),
 
     # Log out
-    url(r'^logout/$', auth_views.LogoutView.as_view(next_page='/'), name='logout'),
+    path('logout/', auth_views.LogoutView.as_view(next_page='/'), name='logout'),
 
     # Password change
-    url(
-        r'^password/$',
+    path(
+        'password/',
         auth_views.PasswordChangeView.as_view(
             template_name="users/change_password.html",
             success_url=reverse_lazy('users:account')
@@ -29,8 +32,8 @@ urlpatterns = [
     ),
 
     # Password reset
-    url(
-        r'^reset/$',
+    path(
+        'reset/',
         auth_views.PasswordResetView.as_view(
             template_name='users/password_reset/form.html',
             email_template_name='users/password_reset/email.txt',
@@ -38,13 +41,13 @@ urlpatterns = [
         ),
         name='password_reset',
     ),
-    url(
-        r'^reset/done/$',
+    path(
+        'reset/done/',
         auth_views.PasswordResetDoneView.as_view(template_name='users/password_reset/done.html'),
         name='password_reset_done'
     ),
-    url(
-        r'^reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
+    path(
+        'reset/confirm/<uidb64>/<token>/',
         auth_views.PasswordResetConfirmView.as_view(
             template_name='users/password_reset/confirm.html',
             post_reset_login=True,
@@ -53,16 +56,16 @@ urlpatterns = [
         ),
         name='password_reset_confirm'
     ),
-    url(
-        r'^reset/complete/$',
+    path(
+        'reset/complete/',
         auth_views.PasswordResetCompleteView.as_view(template_name='users/password_reset/complete.html'),
         name='password_reset_complete'
     ),
-    url(
-        r'^activate/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
+    path(
+        'activate/<uidb64>/<token>/',
         ActivationView.as_view(),
         name='activate'
     ),
-    url(r'^activate/password/', create_password, name="activate_password"),
-    url(r'^oauth$', oauth, name='oauth'),
+    path('activate/password/', create_password, name="activate_password"),
+    path('oauth', oauth, name='oauth'),
 ]
diff --git a/opentech/public/esi/__init__.py b/opentech/public/esi/__init__.py
index f17b8368446f8ae35e5ad6e47fdbab3f2538a577..d4bd564be6488ffd0d5ce092c0f2c3dfc84b63b8 100644
--- a/opentech/public/esi/__init__.py
+++ b/opentech/public/esi/__init__.py
@@ -1,5 +1,5 @@
 from django.conf import settings
-from django.core.urlresolvers import reverse
+from django.urls import reverse
 from django.template.loader import render_to_string
 
 
diff --git a/opentech/public/home/migrations/0001_initial.py b/opentech/public/home/migrations/0001_initial.py
index 12120b66c4d2e91d29c6f2efcb5a7325a0a08d95..fccc58faec595b2863aa3d7bddc94bef959fdc01 100644
--- a/opentech/public/home/migrations/0001_initial.py
+++ b/opentech/public/home/migrations/0001_initial.py
@@ -17,7 +17,7 @@ class Migration(migrations.Migration):
         migrations.CreateModel(
             name='HomePage',
             fields=[
-                ('page_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='wagtailcore.Page')),
+                ('page_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='wagtailcore.Page', on_delete=models.CASCADE)),
                 ('call_to_action', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='utils.CallToActionSnippet')),
                 ('social_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='images.CustomImage')),
                 ('social_text', models.CharField(blank=True, max_length=255)),
diff --git a/opentech/public/home/migrations/0009_django2_update.py b/opentech/public/home/migrations/0009_django2_update.py
new file mode 100644
index 0000000000000000000000000000000000000000..ba0047193bc4dc44d7cfd57b5f0c6db4bde5e38c
--- /dev/null
+++ b/opentech/public/home/migrations/0009_django2_update.py
@@ -0,0 +1,34 @@
+# Generated by Django 2.0.2 on 2018-03-01 21:46
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('home', '0008_add_intro_to_lab_and_funds'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='homepage',
+            name='funds_link',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.Page'),
+        ),
+        migrations.AlterField(
+            model_name='homepage',
+            name='labs_link',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.Page'),
+        ),
+        migrations.AlterField(
+            model_name='homepage',
+            name='our_work_link',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.Page'),
+        ),
+        migrations.AlterField(
+            model_name='homepage',
+            name='strapline_link',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.Page'),
+        ),
+    ]
diff --git a/opentech/public/home/models.py b/opentech/public/home/models.py
index bcc054feafd63e055c6aa80d4ce43befc07948f6..5918dd877c92418cd132950e7b2341234bfb7750 100644
--- a/opentech/public/home/models.py
+++ b/opentech/public/home/models.py
@@ -49,24 +49,24 @@ class HomePage(BasePage):
     NUM_RELATED = 6
 
     strapline = models.CharField(blank=True, max_length=255)
-    strapline_link = models.ForeignKey('wagtailcore.Page', related_name='+')
+    strapline_link = models.ForeignKey('wagtailcore.Page', related_name='+', on_delete=models.PROTECT)
     strapline_link_text = models.CharField(max_length=255)
 
     our_work_title = models.CharField(max_length=255)
     our_work = StreamField([
         ('work', OurWorkBlock()),
     ])
-    our_work_link = models.ForeignKey('wagtailcore.Page', related_name='+')
+    our_work_link = models.ForeignKey('wagtailcore.Page', related_name='+', on_delete=models.PROTECT)
     our_work_link_text = models.CharField(max_length=255)
 
     funds_title = models.CharField(max_length=255)
     funds_intro = models.TextField(blank=True)
-    funds_link = models.ForeignKey('wagtailcore.Page', related_name='+')
+    funds_link = models.ForeignKey('wagtailcore.Page', related_name='+', on_delete=models.PROTECT)
     funds_link_text = models.CharField(max_length=255)
 
     labs_title = models.CharField(max_length=255)
     labs_intro = models.TextField(blank=True)
-    labs_link = models.ForeignKey('wagtailcore.Page', related_name='+')
+    labs_link = models.ForeignKey('wagtailcore.Page', related_name='+', on_delete=models.PROTECT)
     labs_link_text = models.CharField(max_length=255)
 
     search_fields = BasePage.search_fields + [
diff --git a/opentech/public/people/migrations/0005_django2_update.py b/opentech/public/people/migrations/0005_django2_update.py
new file mode 100644
index 0000000000000000000000000000000000000000..bc45d7def2c7c73eead8f953451e2c438d111e66
--- /dev/null
+++ b/opentech/public/people/migrations/0005_django2_update.py
@@ -0,0 +1,19 @@
+# Generated by Django 2.0.2 on 2018-03-01 21:46
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('people', '0004_funding'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='personpagepersontype',
+            name='person_type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='people.PersonType'),
+        ),
+    ]
diff --git a/opentech/public/people/models.py b/opentech/public/people/models.py
index 9d96d4abad3d4dd9858624ab87637d9ab8179a47..2138c9c2b10728c006f95da198f702ca10de01f5 100644
--- a/opentech/public/people/models.py
+++ b/opentech/public/people/models.py
@@ -60,7 +60,8 @@ class PersonPagePersonType(models.Model):
     page = ParentalKey('PersonPage', related_name='person_types')
     person_type = models.ForeignKey(
         'PersonType',
-        related_name='+'
+        related_name='+',
+        on_delete=models.PROTECT,
     )
 
     panels = [
diff --git a/opentech/public/urls.py b/opentech/public/urls.py
index 9caea43477cc74e124ac58d307a36768860d062d..10ac60a78e38cb9fbbff60875a7513e9f4916078 100644
--- a/opentech/public/urls.py
+++ b/opentech/public/urls.py
@@ -1,9 +1,9 @@
-from django.conf.urls import url
+from django.urls import path
 
 from .esi import views as esi_views
 from .search import views as search_views
 
 urlpatterns = [
-    url(r'^esi/(.*)/$', esi_views.esi, name='esi'),
-    url(r'^search/$', search_views.search, name='search'),
+    path('esi/<slug>/', esi_views.esi, name='esi'),
+    path('search/', search_views.search, name='search'),
 ]
diff --git a/opentech/urls.py b/opentech/urls.py
index d514b7c6605527e0ab1b68642e01b5abdd08f97a..39642f5cd555f465ede554847e68869854c3b509 100644
--- a/opentech/urls.py
+++ b/opentech/urls.py
@@ -1,5 +1,5 @@
 from django.conf import settings
-from django.conf.urls import include, url
+from django.urls import include, path
 from django.contrib import admin
 from django.views.decorators.cache import cache_control
 from django.views.generic import TemplateView
@@ -15,16 +15,16 @@ from opentech.apply import urls as apply_urls
 
 
 urlpatterns = [
-    url(r'^django-admin/', include(admin.site.urls)),
-    url(r'^admin/', include(wagtailadmin_urls)),
-
-    url(r'^documents/', include(wagtaildocs_urls)),
-    url('^sitemap\.xml$', sitemap),
-    url('^', include(public_urls)),
-    url('^', include(apply_urls)),
-    url('^', include('social_django.urls', namespace='social')),
-    url(r'^tinymce/', include('tinymce.urls')),
-    url(r'^select2/', include('django_select2.urls')),
+    path('django-admin/', admin.site.urls),
+    path('admin/', include(wagtailadmin_urls)),
+
+    path('documents/', include(wagtaildocs_urls)),
+    path('sitemap\.xml', sitemap),
+    path('', include(public_urls)),
+    path('', include(apply_urls)),
+    path('', include('social_django.urls', namespace='social')),
+    path('tinymce/', include('tinymce.urls')),
+    path('select2/', include('django_select2.urls')),
 ]
 
 
@@ -38,18 +38,18 @@ if settings.DEBUG:
 
     urlpatterns += [
         # Add views for testing 404 and 500 templates
-        url(r'^test404/$', TemplateView.as_view(template_name='404.html')),
-        url(r'^test500/$', TemplateView.as_view(template_name='500.html')),
+        path('test404/', TemplateView.as_view(template_name='404.html')),
+        path('test500/', TemplateView.as_view(template_name='500.html')),
     ]
 
 if settings.DEBUG or settings.ENABLE_STYLEGUIDE:
     urlpatterns += [
         # Add styleguide
-        url(r'^styleguide/$', TemplateView.as_view(template_name='styleguide.html')),
+        path('styleguide/', TemplateView.as_view(template_name='styleguide.html')),
     ]
 
 urlpatterns += [
-    url(r'', include(wagtail_urls)),
+    path('', include(wagtail_urls)),
 ]
 
 
diff --git a/requirements.txt b/requirements.txt
index a92cc7780d78f114c7fa8180a0d06228da28d844..f7261ca4daf3d46753dab3816a9892d321124d20 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,10 +1,11 @@
 Django==2.0.2
+djangorestframework==3.7.4
 wagtail==2.0
 psycopg2==2.7.3.1
 Pillow==4.3.0
 django-bleach==0.3.0
-django-extensions==1.7.4
-django-countries==5.1
+django-extensions==2.0.0
+django-countries==5.12.0.0
 Werkzeug==0.11.11
 stellar==0.4.3
 django-tinymce4-lite==1.7.0