---
slug: "django-oauth2_provider-migration-failed-foreignkey"
title: "After Updating Django, Migration Fails for oauth2_provider"
description: "Use `mkdir -p ... && cd $_` on macOS to chain create-and-enter reliably without race conditions."
url: "https://www.ytyng.com/en/blog/django-oauth2_provider-migration-failed-foreignkey"
publish_date: "2022-09-16T10:11:48Z"
created: "2022-09-16T10:11:48Z"
updated: "2026-05-11T13:21:53.439Z"
categories: ["Django"]
keywords: ""
featured_image_url: "https://media.ytyng.com/resize/20230812/489b5b091ee94711a39dc15654f907c3.png.webp?width=768"
has_video: false
has_music: false
video_urls: []
music_urls: []
lang: "en"
---

# After Updating Django, Migration Fails for oauth2_provider

<h1>Background</h1>
<p>The migration for the oauth2_provider app included in Django's django-oauth-toolkit failed at 0004.</p>
<p>When running <code>./manage.py migrate oauth2_provider</code>, an exception is thrown due to the inability to create the Foreign Key.</p>
<p>If the database migration stops midway, the DDL ends up in an incomplete state, making recovery quite tedious.</p>
<pre>SHOW CREATE TABLE oauth2_provider_application;</pre>
<p>Check if it shows</p>
<pre>`id` bigint(20) NOT NULL AUTO_INCREMENT,</pre>
<p>If it shows</p>
<pre>`id` int(11) NOT NULL AUTO_INCREMENT,</pre>
<p>then creating the FK constraint <code>oauth2_provider_idtoken.application_id</code> &rarr; <code>oauth2_provider_application.id</code> will fail because the data types don't match.</p>
<p>The likely cause is that in the past, when using an older version of Django, the migration for oauth2_provider was progressed up to 0003, resulting in <code>oauth2_provider_application.id</code> being created as int(11).</p>
<p>In recent versions of oauth2_provider, the id field is created as bigint(20), making it incompatible with the older int(11) for FK creation.</p>
<p>To enable FK creation, it's necessary to ALTER TABLE from int(11) to bigint(20). However, since FK constraints already exist between the existing ids, changing from int to bigint is a <strong>very tedious</strong> task.</p>

<h1>Work Description</h1>
<h2>1. Ignore the fact that migration 0004 stopped midway due to an exception. Do not revert to 0003.</h2>
<p>Trying to revert will only complicate things, so for now, forget about the migration.</p>

<h2>2. Record the SHOW CREATE TABLE of all oauth2_provider tables as text.</h2>
<p>It will likely look something like this.</p>
<pre>show create table oauth2_provider_accesstoken;
CREATE TABLE `oauth2_provider_accesstoken` (
 `id` bigint(20) NOT NULL AUTO_INCREMENT,
 `token` varchar(255) NOT NULL,
 `expires` datetime(6) NOT NULL,
 `scope` longtext NOT NULL,
 `application_id` bigint(20) DEFAULT NULL,
 `user_id` int(11) DEFAULT NULL,
 `created` datetime(6) NOT NULL,
 `updated` datetime(6) NOT NULL,
 `source_refresh_token_id` bigint(20) DEFAULT NULL,
 `id_token_id` bigint(20) DEFAULT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `token` (`token`),
 UNIQUE KEY `source_refresh_token_id` (`source_refresh_token_id`),
 UNIQUE KEY `id_token_id` (`id_token_id`),
 KEY `oauth2_provider_accesstoken_user_id_6e4c9a65_fk_user_user_id` (`user_id`),
 CONSTRAINT `oauth2_provider_acce_id_token_id_85db651b_fk_oauth2_pr` FOREIGN KEY (`id_token_id`) REFERENCES `oauth2_provider_idtoken` (`id`),
 CONSTRAINT `oauth2_provider_acce_source_refresh_token_e66fbc72_fk_oauth2_pr` FOREIGN KEY (`source_refresh_token_id`) REFERENCES `oauth2_provider_refreshtoken` (`id`),
 CONSTRAINT `oauth2_provider_accesstoken_user_id_6e4c9a65_fk_user_user_id` FOREIGN KEY (`user_id`) REFERENCES `user_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8</pre>

<pre>show create table oauth2_provider_application;
CREATE TABLE `oauth2_provider_application` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `client_id` varchar(100) NOT NULL,
 `redirect_uris` longtext NOT NULL,
 `client_type` varchar(32) NOT NULL,
 `authorization_grant_type` varchar(32) NOT NULL,
 `client_secret` varchar(255) NOT NULL,
 `name` varchar(255) NOT NULL,
 `user_id` int(11) NOT NULL,
 `skip_authorization` tinyint(1) NOT NULL,
 `algorithm` varchar(5) NOT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `client_id` (`client_id`),
 KEY `oauth2_provider_application_9d667c2b` (`client_secret`),
 KEY `oauth2_provider_application_user_id_79829054_fk_user_user_id` (`user_id`),
 CONSTRAINT `oauth2_provider_application_user_id_79829054_fk_user_user_id` FOREIGN KEY (`user_id`) REFERENCES `user_user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8</pre>

<pre>show create table oauth2_provider_grant;
CREATE TABLE `oauth2_provider_grant` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `code` varchar(255) NOT NULL,
 `expires` datetime(6) NOT NULL,
 `redirect_uri` longtext NOT NULL,
 `scope` longtext NOT NULL,
 `application_id` int(11) NOT NULL,
 `user_id` int(11) NOT NULL,
 `code_challenge` varchar(128) NOT NULL,
 `code_challenge_method` varchar(10) NOT NULL,
 `nonce` varchar(255) NOT NULL,
 `claims` longtext NOT NULL,
 PRIMARY KEY (`id`),
 KEY `oauth2_application_id_81923564_fk_oauth2_provider_application_id` (`application_id`),
 KEY `oauth2_provider_grant_user_id_e8f62af8_fk_user_user_id` (`user_id`),
 KEY `oauth2_provider_grant_c1336794` (`code`),
 CONSTRAINT `oauth2_application_id_81923564_fk_oauth2_provider_application_id` FOREIGN KEY (`application_id`) REFERENCES `oauth2_provider_application` (`id`),
 CONSTRAINT `oauth2_provider_grant_user_id_e8f62af8_fk_user_user_id` FOREIGN KEY (`user_id`) REFERENCES `user_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8</pre>

<pre>show create table oauth2_provider_idtoken;
CREATE TABLE `oauth2_provider_idtoken` (
 `id` bigint(20) NOT NULL AUTO_INCREMENT,
 `jti` char(32) NOT NULL,
 `expires` datetime(6) NOT NULL,
 `scope` longtext NOT NULL,
 `created` datetime(6) NOT NULL,
 `updated` datetime(6) NOT NULL,
 `application_id` bigint(20) DEFAULT NULL,
 `user_id` int(11) DEFAULT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `jti` (`jti`),
 KEY `oauth2_provider_idtoken_user_id_dd512b59_fk_user_user_id` (`user_id`),
 CONSTRAINT `oauth2_provider_idtoken_user_id_dd512b59_fk_user_user_id` FOREIGN KEY (`user_id`) REFERENCES `user_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8</pre>

<pre>show create table oauth2_provider_refreshtoken;
CREATE TABLE `oauth2_provider_refreshtoken` (
 `id` bigint(20) NOT NULL AUTO_INCREMENT,
 `token` varchar(255) NOT NULL,
 `access_token_id` bigint(20) DEFAULT NULL,
 `application_id` bigint(20) NOT NULL,
 `user_id` int(11) NOT NULL,
 `created` datetime(6) NOT NULL,
 `updated` datetime(6) NOT NULL,
 `revoked` datetime(6) DEFAULT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `access_token_id` (`access_token_id`),
 UNIQUE KEY `oauth2_provider_refreshtoken_token_revoked_af8a5134_uniq` (`token`,`revoked`),
 KEY `oauth2_provider_refreshtoken_user_id_da837fce_fk_user_user_id` (`user_id`),
 CONSTRAINT `oauth2_provider_refr_access_token_id_775e84e8_fk_oauth2_pr` FOREIGN KEY (`access_token_id`) REFERENCES `oauth2_provider_accesstoken` (`id`),
 CONSTRAINT `oauth2_provider_refreshtoken_user_id_da837fce_fk_user_user_id` FOREIGN KEY (`user_id`) REFERENCES `user_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8</pre>

<h2>3. Remove all Foreign Key Constraints between oauth2_provider tables</h2>
<pre>alter table oauth2_provider_accesstoken
 drop foreign key oauth2_provider_acce_id_token_id_85db651b_fk_oauth2_pr,
 drop foreign key oauth2_provider_acce_source_refresh_token_e66fbc72_fk_oauth2_pr;</pre>

<pre>alter table oauth2_provider_grant
 drop foreign key oauth2_application_id_81923564_fk_oauth2_provider_application_id;</pre>

<pre>alter table oauth2_provider_refreshtoken
 drop foreign key oauth2_provider_refr_access_token_id_775e84e8_fk_oauth2_pr,
 drop foreign key oauth2_provider_refreshtoken_user_id_da837fce_fk_user_user_id;</pre>
<p>Remove them this way. Compare with the actual state of the tables while doing this.</p>

<h2>4. Change int to bigint</h2>
<pre>alter table oauth2_provider_accesstoken
 modify id bigint auto_increment;</pre>

<pre>alter table oauth2_provider_application
 modify id bigint auto_increment;</pre>

<pre>alter table oauth2_provider_grant
 modify id bigint auto_increment,
 modify application_id bigint NOT NULL;</pre>
<p>Do it similarly. This might not cover everything.</p>

<h2>5. Recreate the FKs</h2>
<p>Use the text created in step 2 to construct the alter table statements.</p>
<pre>alter table oauth2_provider_accesstoken
 add CONSTRAINT `oauth2_provider_acce_id_token_id_85db651b_fk_oauth2_pr` FOREIGN KEY (`id_token_id`) REFERENCES `oauth2_provider_idtoken` (`id`),
 add CONSTRAINT `oauth2_provider_acce_source_refresh_token_e66fbc72_fk_oauth2_pr` FOREIGN KEY (`source_refresh_token_id`) REFERENCES `oauth2_provider_refreshtoken` (`id`);</pre>

<pre>alter table oauth2_provider_grant
 add CONSTRAINT `oauth2_application_id_81923564_fk_oauth2_provider_application_id` FOREIGN KEY (`application_id`) REFERENCES `oauth2_provider_application` (`id`);</pre>

<pre>alter table oauth2_provider_refreshtoken
 add CONSTRAINT `oauth2_provider_refr_access_token_id_775e84e8_fk_oauth2_pr` FOREIGN KEY (`access_token_id`) REFERENCES `oauth2_provider_accesstoken` (`id`),
 add CONSTRAINT `oauth2_provider_refreshtoken_user_id_da837fce_fk_user_user_id` FOREIGN KEY (`user_id`) REFERENCES `user_user` (`id`);</pre>

<h2>6. Manually execute migration 0004</h2>
<p>This is the content of 0004. If an error occurred and it failed, it means it succeeded partially. Everything is idempotent, so issuing the SQL again is not a problem.</p>
<pre>ALTER TABLE `oauth2_provider_application` ALTER COLUMN `algorithm` DROP DEFAULT;
CREATE TABLE `oauth2_provider_idtoken` (`id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY, `jti` char(32) NOT NULL UNIQUE, `expires` datetime(6) NOT NULL, `scope` longtext NOT NULL, `created` datetime(6) NOT NULL, `updated` datetime(6) NOT NULL, `application_id` bigint NULL, `user_id` integer NULL);
ALTER TABLE `oauth2_provider_accesstoken` ADD COLUMN `id_token_id` bigint NULL UNIQUE , ADD CONSTRAINT `oauth2_provider_acce_id_token_id_85db651b_fk_oauth2_pr` FOREIGN KEY (`id_token_id`) REFERENCES `oauth2_provider_idtoken`(`id`);
ALTER TABLE `oauth2_provider_grant` ADD COLUMN `nonce` varchar(255) DEFAULT '' NOT NULL;
ALTER TABLE `oauth2_provider_grant` ALTER COLUMN `nonce` DROP DEFAULT;
ALTER TABLE `oauth2_provider_grant` ADD COLUMN `claims` longtext NOT NULL;
ALTER TABLE `oauth2_provider_idtoken` ADD CONSTRAINT `oauth2_provider_idto_application_id_08c5ff4f_fk_oauth2_pr` FOREIGN KEY (`application_id`) REFERENCES `oauth2_provider_application` (`id`);
ALTER TABLE `oauth2_provider_idtoken` ADD CONSTRAINT `oauth2_provider_idtoken_user_id_dd512b59_fk_user_user_id` FOREIGN KEY (`user_id`) REFERENCES `user_user` (`id`);</pre>

<h2>7. Mark migration 0004 as completed with a fake migration</h2>
<pre>./manage.py showmigrations oauth2_provider
./manage.py migrate oauth2_provider 0004 --fake
./manage.py showmigrations oauth2_provider</pre>

<h2>8. Complete the migration</h2>
<pre>./manage.py migrate oauth2_provider</pre>
<p>In my case,</p>
<pre>django.db.utils.OperationalError: (1054, "Unknown column 'oauth2_provider_application.created' in 'field list'")</pre>
<p>occurred, so</p>
<pre>ALTER TABLE oauth2_provider_application
ADD COLUMN `created` datetime(6),
ADD COLUMN `updated` datetime(6);</pre>
<p>I executed this. Since created and updated are fields in the abstract class of oauth2_provider, similar columns need to be added to all tables of oauth2_provider.</p>
