smhk

Removing a third-party Django app and its tables

Removing an app from Django is fairly straight-forward, if its an internal app, i.e. one for which you control the source code. In a nutshell, you delete your models, then run makemigrations to generate a migration that deletes the tables from the database, and then you can delete the app.

But if it is an external (third-party) app, i.e. one for which you don’t control the source code, the same process isn’t possible. Since the migrations for deleting the tables cannot be added to the app that you don’t have control over.

I had assumed it would be a common use-case to want to remove an app from Django, and to also remove all its associated tables. However, either my search-fu has failed me, or it’s not as common as I had assumed.

There are some thorough answers, but again they either assume it’s an internal app, or that you’re running an old version of Django (e.g. pre v3.0.0).

The official documentation provides details on how to deprecate model fields before removing them, but that’s not quite what we want. The advanced migration documentation has a guide for migrating data between apps before uninstalling the old app, but doesn’t cover the details of uninstalling the old app.

I did find one question specifically about writing migrations for external apps, and the answer suggests writing the migration in one of your internal apps but depending upon the external app. However, if our goal is to remove the external app, that might end up being more of a hindrance.

While not ideal, the best solution I found builds upon this previous answer, but uses raw SQL to clean up the app rather than going through the Django ORM. On the upside, this avoids any dependencies, and works regardless of whether the tables for deletion exist already or not. The downside, using raw SQL won’t necessarily work for all databases, among other caveats.

Regardless, a migration such as the following will work for both MySQL and SQLite. The following example is for removing Tastypie:

from django.db import migrations

class Migration(migrations.Migration):
    dependencies = [("my_app", "0042_previous_migration")]
    operations = [
        migrations.RunSQL(
            """
            DROP TABLE IF EXISTS tastypie_apikey;
            DROP TABLE IF EXISTS tastypie_apiaccess;
            DELETE FROM auth_permission WHERE content_type_id IN (SELECT id FROM django_content_type WHERE app_label = '{app_label}');
            DELETE FROM django_admin_log WHERE content_type_id IN (SELECT id FROM django_content_type WHERE app_label = '{app_label}');
            DELETE FROM django_content_type WHERE app_label = '{app_label}';
            DELETE FROM django_migrations WHERE app = '{app_label}';
            """.format(
                app_label="tastypie"
            )
        )
    ]

Note that this deletes the Tastypie tables and content types. Arguably, deleting the content types is optional. This could also be done with the django-admin remove_stale_contenttypes command, but including it in the migration makes the process automatic and only targets this app.