Django Admin: Deleting Objects Doesn't Use the Correct Database in a Multi-Database Setup

I have a Django project with multiple databases configured in settings.py. My User model is stored in the "members" database, and I can list and save objects correctly in Django Admin. However, deleting objects does not use the "members" database, and I get an error saying the table doesn’t exist.
django.db.utils.ProgrammingError: relation "login_history" does not exist LINE 1: ...rs"."premium_expired", "users"."location_id" FROM "login_his...

class MultiDBModelAdmin(admin.ModelAdmin):
    using = "members"

    def save_model(self, request, obj, form, change):
        # Tell Django to save objects to the 'members' database.
        obj.save(using=self.using)

    def delete_model(self, request, obj):
        # Tell Django to delete objects from the 'members' database
        obj._state.db = self.using
        obj.delete(using=self.using)
    
    def delete_queryset(self, request, queryset):
        queryset.using(self.using).delete()

    def get_queryset(self, request):
        # Tell Django to look for objects on the 'members' database.
        return super().get_queryset(request).using(self.using)

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        # Tell Django to populate ForeignKey widgets using a query
        # on the 'members' database.
        return super().formfield_for_foreignkey(
            db_field, request, using=self.using, **kwargs
        )

    def formfield_for_manytomany(self, db_field, request, **kwargs):
        # Tell Django to populate ManyToMany widgets using a query
        # on the 'members' database.
        return super().formfield_for_manytomany(
            db_field, request, using=self.using, **kwargs
        )
    
    def _save_related(self, request, form, formsets, change):
        """Ensure related objects are saved in the correct database."""
        for formset in formsets:
            formset.instance._state.db = self.using  # Force the database
        super()._save_related(request, form, formsets, change)

class MultiDBTabularInline(admin.StackedInline):
    using = "members"

    def get_queryset(self, request):
        # Tell Django to look for inline objects on the 'members' database.
        return super().get_queryset(request).using(self.using)

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        # Tell Django to populate ForeignKey widgets using a query
        # on the 'members' database.
        return super().formfield_for_foreignkey(
            db_field, request, using=self.using, **kwargs
        )

    def formfield_for_manytomany(self, db_field, request, **kwargs):
        # Tell Django to populate ManyToMany widgets using a query
        # on the 'members' database.
        return super().formfield_for_manytomany(
            db_field, request, using=self.using, **kwargs
        )

    def has_change_permission(self, request, obj = ...):
        return False

class LoginHistoryInline(MultiDBTabularInline):
    model = LoginHistoryRecord
    readonly_fields = [
        "id",
        "device",
        "login_date",
        "device_type",
    ]
    can_delete = False
    verbose_name = "Login history"
    verbose_name_plural = "Login history"


@admin.register(User)
class MemberAdmin(MultiDBModelAdmin):
    list_display = [
        "id",
    ]

How can I ensure that Django Admin deletes objects from the correct database (members) instead of the default database? Is there something else I need to override to make deletion work properly?

In the future, when asking for assistance with an error, please post the complete error with the full traceback.

Without the full error and traceback it’s tough to tell, but a common cause of this type of error is having created a new User model or moved the User model to a new database after having run the initial migration. (But I can’t be sure that that is the issue here without seeing the full error & traceback.)

I do see that you don’t have a using attribute on your LoginHistoryInline. Is that model also stored in the members database?

It would be helpful also if you posted your User and LoginHistoryRecord models.

Do you have a custom router defined for these models?

Thank you for your reply!

Here is my full traceback:

Traceback (most recent call last):
File "/usr/local/lib/python3.11/site-packages/django/contrib/staticfiles/handlers.py", line 80, in __call__
  return self.application(environ, start_response)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/core/handlers/wsgi.py", line 124, in __call__
  response = self.get_response(request)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/core/handlers/base.py", line 140, in get_response
  response = self._middleware_chain(request)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/core/handlers/exception.py", line 57, in inner
  response = response_for_exception(request, exc)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/core/handlers/exception.py", line 140, in response_for_exception
  response = handle_uncaught_exception(
             ^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/core/handlers/exception.py", line 181, in handle_uncaught_exception
  return debug.technical_500_response(request, *exc_info)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django_extensions/management/technical_response.py", line 40, in null_technical_500_response
  raise exc_value.with_traceback(tb)
File "/usr/local/lib/python3.11/site-packages/django/core/handlers/exception.py", line 55, in inner
  response = get_response(request)
             ^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/core/handlers/base.py", line 197, in _get_response
  response = wrapped_callback(request, *callback_args, **callback_kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/contextlib.py", line 81, in inner
  return func(*args, **kwds)
         ^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/contrib/admin/options.py", line 688, in wrapper
  return self.admin_site.admin_view(view)(*args, **kwargs)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/utils/decorators.py", line 134, in _wrapper_view
  response = view_func(request, *args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/views/decorators/cache.py", line 62, in _wrapper_view_func
  response = view_func(request, *args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/contrib/admin/sites.py", line 242, in inner
  return view(request, *args, **kwargs)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/utils/decorators.py", line 46, in _wrapper
  return bound_method(*args, **kwargs)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/utils/decorators.py", line 134, in _wrapper_view
  response = view_func(request, *args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/contrib/admin/options.py", line 2106, in delete_view
  return self._delete_view(request, object_id, extra_context)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/contrib/admin/options.py", line 2133, in _delete_view
  ) = self.get_deleted_objects([obj], request)
      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/contrib/admin/options.py", line 2101, in get_deleted_objects
  return get_deleted_objects(objs, request, self.admin_site)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/contrib/admin/utils.py", line 121, in get_deleted_objects
  collector.collect(objs)
File "/usr/local/lib/python3.11/site-packages/django/contrib/admin/utils.py", line 187, in collect
  return super().collect(objs, source_attr=source_attr, **kwargs)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/db/models/deletion.py", line 348, in collect
  if getattr(on_delete, "lazy_sub_objs", False) or sub_objs:
File "/usr/local/lib/python3.11/site-packages/django/db/models/query.py", line 412, in __bool__
  self._fetch_all()
File "/usr/local/lib/python3.11/site-packages/django/db/models/query.py", line 1881, in _fetch_all
  self._result_cache = list(self._iterable_class(self))
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/db/models/query.py", line 91, in __iter__
  results = compiler.execute_sql(
            ^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/db/models/sql/compiler.py", line 1562, in execute_sql
  cursor.execute(sql, params)
File "/usr/local/lib/python3.11/site-packages/debug_toolbar/panels/sql/tracking.py", line 235, in execute
  return self._record(super().execute, sql, params)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/debug_toolbar/panels/sql/tracking.py", line 160, in _record
  return method(sql, params)
         ^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/db/backends/utils.py", line 102, in execute
  return super().execute(sql, params)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/db/backends/utils.py", line 67, in execute
  return self._execute_with_wrappers(
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/db/backends/utils.py", line 80, in _execute_with_wrappers
  return executor(sql, params, many, context)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/django/db/backends/utils.py", line 84, in _execute
  with self.db.wrap_database_errors:
File "/usr/local/lib/python3.11/site-packages/django/db/utils.py", line 91, in __exit__
  raise dj_exc_value.with_traceback(traceback) from exc_value
File "/usr/local/lib/python3.11/site-packages/django/db/backends/utils.py", line 89, in _execute
  return self.cursor.execute(sql, params)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/psycopg/cursor.py", line 732, in execute
  raise ex.with_traceback(None)
django.db.utils.ProgrammingError: relation "login_history" does not exist
LINE 1: ...rs"."premium_expired", "users"."location_id" FROM "login_his...

And here are my models:

class MembersManager(Manager):
    def get_queryset(self, *args, **kwargs):
        return super().get_queryset(*args, **kwargs).using("members")

class LoginHistoryRecord(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.ForeignKey("User", on_delete=models.CASCADE)
    device = models.ForeignKey("Device", on_delete=models.CASCADE)
    login_date = models.DateTimeField(default=timezone.now)
    device_type = models.CharField(max_length=255, default="web")
    
    objects = MembersManager()
    ...

class User(models.Model):
    id = models.BigIntegerField(primary_key=True)
    phone_number = models.CharField(max_length=14, unique=True)
    email = models.EmailField(max_length=255, unique=True, null=True, blank=True)
    ...
    objects = MembersManager()

I am not using a custom database router for my models. The database selection is handled directly in the Manager and Admin classes.
The admin panel displays the objects correctly, and I can save changes to them, after adding a custom Manager to my models, saving started working correctly on the "members" database. However, deleting still does not work.