3

I am getting a django.db.utils.ProgrammingError: relation "user" does not exist error when running createsuperuser on a Django project with a Postgresql database.

I wrote the following database router to indicate that the table user (which is based on a custom extension of the AbstractUser class) is in the schema users. Even so, Django cannot find it.

from myapp.models import Class1, Class2, Class3
from users.models import User
from django.contrib.admin.models import LogEntry
from django.contrib.contenttypes.models import ContentType
from django.contrib.sessions.models import Session

# Include here classes (i.e. tables) that belongs to the "myapp" schema
ROUTED_MODELS_MYAPP = [Class1, Class2, Class3]

# Include here classes (i.e. tables) that belongs to the "users" schema
ROUTED_MODELS_USERS = [User, LogEntry, ContentType, Session] #classes for custom user model, and django tables `django_admin_log`, `django_content_type`, `django_session` tables
# classes for the following table names still missing from list: `auth_group`, `auth_group_permissions`, `auth_permission`.

class MyDBRouter(object):
    """
    A router to place DB queries into correct schema depending on considered tables.
    Sources: 
        https://stackoverflow.com/a/51007441/3976696
        https://www.amvtek.com/blog/posts/2014/Jun/13/accessing-multiple-postgres-schemas-from-django/
    """
    def db_for_read(self, model, **hints):
        if model in ROUTED_MODELS_MYAPP:
            return 'myapp'
        elif model in ROUTED_MODELS_USERS:
            return 'users'
        return None

    def db_for_write(self, model, **hints):
        if model in ROUTED_MODELS_MYAPP:
            return 'myapp'
        elif model in ROUTED_MODELS_USERS:
            return 'users'        
        return None   

The router works for other tables unrelated to authentication, so I suspect this has to do with the other tables automatically created by Django when I migrated the User class for the first time (auth_group, auth_group_permissions, auth_permission, django_admin_log, django_content_type, django_session).

However, I'm not sure:

  1. If the router is supposed to be written like I did (a sequence of if/elif) --> is there a better way to write a router for more than one schema?
  2. Which are the model names corresponding to the Django tables listed above. --> I was able to guess the three last class names (django_admin_log, django_content_type, django_session) by looking into django/contrib directories, but how am I supposed to find those for auth_group, auth_group_permissions, auth_permission?

EDIT: based on the comment from @Kevin, I tried writing the routers based on app_labels rather than model names, as shown in the docs, making one router for each app involved. I also tried manually specifying the app_label in the Meta class of my User class (i.e. app_label = 'users').

However the original error (django.db.utils.ProgrammingError: relation "user" does not exist) persists when I enter a username in createsuperuser. How else am I supposed to handle this situation in the router?

#Route all models in admin application, cf. https://docs.djangoproject.com/en/2.1/topics/db/multi-db/
class AdminRouter:
    """
    A router to control all database operations on models in the admin application.
    """
    def db_for_read(self, model, **hints):
        """
        Attempts to read admin models go to users.
        """
        if model._meta.app_label == 'admin':
            return 'users'
        return None

    def db_for_write(self, model, **hints):
        """
        Attempts to write admin models go to users.
        """
        if model._meta.app_label == 'admin':
            return 'users'
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        """
        Make sure the admin app only appears in the 'users' database.
        """
        if app_label == 'admin':
            return db == 'users'
        return None

class AuthRouter:
    """
    A router to control all database operations on models in the
    auth application.
    """
    <similar to previous router>

class ContentTypeRouter:
    """
    A router to control all database operations on models in the
    contenttype application.
    """
    <similar to previous router>

class SessionRouter:
    """
    A router to control all database operations on models in the
    sessionapplication.
    """
    <similar to previous router>

#Route all models in users application, cf. https://docs.djangoproject.com/en/2.1/topics/db/multi-db/
class UsersRouter:
    """
    A router to control all database operations on models in the users application.
    """
    def db_for_read(self, model, **hints):
        """
        Attempts to read user models go to users.
        """
        if model._meta.app_label == 'users':
            return 'users'
        return None

    def db_for_write(self, model, **hints):
        """
        Attempts to write user models go to users.
        """
        if model._meta.app_label == 'users':
            return 'users'
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        """
        Make sure the user app only appears in the 'users' database.
        """
        if app_label == 'users':
            return db == 'users'
        return None

Then, I call them from settings.py in the following order:

DATABASE_ROUTERS = (
                    'urbio.dbrouters.AdminRouter',
                    'urbio.dbrouters.AuthRouter', 
                    'urbio.dbrouters.ContentTypeRouter', 
                    'urbio.dbrouters.SessionRouter', 
                    'urbio.dbrouters.UsersRouter',
                    )
sc28
  • 1,163
  • 4
  • 26
  • 48
  • Instead of listing the models, it would seem easier to use the method shown in [the documentation](https://docs.djangoproject.com/en/dev/topics/db/multi-db/#an-example): `if model._meta.app_label == 'auth':`. – Kevin Christopher Henry Feb 26 '19 at 00:42
  • @KevinChristopherHenry thanks for the suggestion. I tried that (see edit in original post), but unfortunately it didn't seem to work ether. Would you have any other ideas? – sc28 Feb 26 '19 at 10:16

1 Answers1

2

The solution was to specify the --database flag and point to the correct schema when running the createsuperuser command:

python manage.py createsuperuser --database users

Also, following this answer, the database definition in settings.py is:

DATABASES = {

    'default': 
            {},

    'schema1': 
            {
            'ENGINE': 'django.db.backends.postgresql_psycopg2',
            'OPTIONS': {
                            'options': '-c search_path=schema1'
                        },
            'NAME': 'mydbname',
            'USER': 'myusername',
            'PASSWORD': '***',
            'HOST': 'my.host.address',
            'PORT': '5432',
            },

    'users': 
            {
            'ENGINE': 'django.db.backends.postgresql_psycopg2',
            'OPTIONS': {
                            'options': '-c search_path=users'
                        },
            'NAME': 'mydbname',
            'USER': 'myusername',
            'PASSWORD': '***',
            'HOST': 'my.host.address',
            'PORT': '5432',
            }

}
sc28
  • 1,163
  • 4
  • 26
  • 48