Cleanly removing a Django app

When a Django application is “installed” using the syncdb admin command, some additional artifacts are usually created: ContentType objects and permissions for each model class in your app.  So, if you want to completely “uninstall” an app from a Django project, you have to not only manually remove the database tables, you also have to manually remove these related artifacts.  While the existence of these “orphaned” objects does not seem harmful, I like to keep my database as cruft-free as possible.  A straightforward process for cleanly removing a Django app from a project is as follows:

1. Write “drop table” statements for the app to a file (assuming the working directory is the Django project directory):

$ ./manage.py sqlclear appname > drop_appname_tables.sql

2. Remove the app from the INSTALLED_APPS list in the Django project settings module.

3. From the Django project root URLconf remove the explicit import of the app admin module, if present, and any associated url patterns.

4. Restart the web server or WSGI daemon and confirm that Django is not broken. 🙂

5. Drop the database tables (assuming MySQL backend and Django db name “django”):

$ mysql -p django < drop_appname_tables.sql

6. Clean up the app content type and permission artifacts:

$ ./manage.py shell
>>> from django.contrib.contenttypes.models import ContentType
>>> for ct in ContentType.objects.filter(app_label=appname):
...     ct.delete()
...
>>>

Because Django deletes related objects by default, deleting the ContentType objects will also remove the related django.contrib.auth.models.Permission objects (and the many-to-many associations with users and groups).

Here’s a custom admin command I have used to perform the content types cleanup:

from django.contrib.contenttypes.models import ContentType
from django.core.management.base import LabelCommand

class Command(LabelCommand):

    help = 'Removes legacy content types and related objects for an app that is no longer installed.'
    args = '<app_label app_label ...>'
    label = 'app_label'

    def handle_label(self, label, **options):
        cts = ContentType.objects.filter(app_label=label)
        try:                   # Django 1.2+
            out = self.stdout
        except AttributeError: # Django < 1.2 fallback
            import sys
            out = sys.stdout
        if len(cts):
            out.write('Deleting content types for app_label "%s" ...\n' % label)
            for ct in cts:
                out.write('Deleting %r and related objects ...\n' % ct)
                ct.delete()
        else:
            out.write('No content types found for app_label "%s".\n' % label)

Note that the app_label is not necessarily the same as the string used in INSTALLED_APPS.

Advertisements
  1. #1 by JeremyT on September 23, 2010 - 4:12 pm

    Nice. I’ve been manually dropping tables, didn’t even think to look for a better way 🙂

  2. #2 by Lacrymology on March 30, 2012 - 1:17 pm

    Thanks. Just one thing, you could write that as simply `ContentType.objects.filter(app_label=appname).delete()`