AUTH_USER_MODEL error in production only: AUTH_USER_MODEL refers

ghz 9hours ago ⋅ 2 views

AUTH_USER_MODEL error in production only: AUTH_USER_MODEL refers to model 'auth.User' that has not been installed

I have my app working on localhost but when i deploy to production (Heroku), I get this error:

File "./blog/models.py", line 6, in <module>
2021-03-24T23:31:05.696194+00:00 app[web.1]: User = get_user_model()
2021-03-24T23:31:05.696219+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.6/site-packages/django/contrib/auth/__init__.py", line 162, in get_user_model
2021-03-24T23:31:05.696372+00:00 app[web.1]: "AUTH_USER_MODEL refers to model '%s' that has not been installed" % settings.AUTH_USER_MODEL
2021-03-24T23:31:05.696397+00:00 app[web.1]: django.core.exceptions.ImproperlyConfigured: AUTH_USER_MODEL refers to model 'auth.User' that has not been installed

I have tried countless configurations, such as setting AUTH_USER_MODEL = 'auth.User' in settings after doing from django.contrib.auth.models import User and:

from django.contrib.auth import get_user_model
User = get_user_model()

but nothing works.

settings.py

"""~~~NOTES~~~
- procfile could use channel_layer instead of channels
- AUTH_USER_MODEL could be 'django_project.User'. Worked locally but not on heroku
- django.setup() seems ok at bottom of settings.py
- If have to

"""

import django
from django.core.wsgi import get_wsgi_application
from django.core.asgi import get_asgi_application
# from django.contrib.auth.models import User #todo: this causes ImproperlyConfigured: SECRET_KEY MUST NOT BE EMPTY

import os
import django_heroku

DJANGO_SETTINGS_MODULE = 'django_project.settings'
SECRET_KEY = 'asdaf123$9pv98=e6p^gl(-eoj' #todo: test removing this in own deployment
# AUTH_USER_MODEL=User # todo: this causes RuntimeError: populate() isnt reentrant
#SECRET_KEY = os.environ.get('SECRET_KEY')

DEBUG = 'True'

ALLOWED_HOSTS = ['*', 'localhost', '127.0.0.1']

INSTALLED_APPS = [
    'django.contrib.auth',
    'blog.apps.BlogConfig', #allows Django to correctly search your templates for the 'blog' app
    'users.apps.UsersConfig',
    # 'chat.apps.ChatConfig',
    'chat',
    'crispy_forms',
    'channels',
    'dal',
    'dal_select2',
    'django.contrib.admin',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    # 'django_messages',
    'django.contrib.staticfiles',
    'storages'
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'django_project.urls'

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, '')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
            # 'libraries': {
            #     'staticfiles':'chat.templatetags.__init__.py'
            # }
        },
    },
]

SETTINGS_PATH = os.path.join(os.path.dirname(__file__) ,'../templates').replace('\\','/')
TEMPLATE_DIRS = ( # deprecated
    os.path.join(SETTINGS_PATH, 'blog/templates'), # Django will look at the templates from templates/ directory under your project
)

# ~~~MESSAGES CONFIG~~~
WSGI_APPLICATION = 'django_project.wsgi.application'
ASGI_APPLICATION = 'django_project.asgi.application' # older version of django: 'django_project.routing.application'

# Channels redis config:
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            #"hosts": [('127.0.0.1', 6379)], or 'redis' #l ocal
            "hosts": ['rediss://:p628bf20dab326cedb30d4df129e9691dbb6e7e1f4486954eadbfdf77db854369@ec2-34-235-242-69.compute-1.amazonaws.com:25180'], # REDIS_TLS_URL #todo: confirm. Changed from "127.0.0.1" to 'redis'... found promising answer, changing this
            # 'redis://:p628bf20dab326cedb30d4df129e9691dbb6e7e1f4486954eadbfdf77db854369@ec2-34-235-242-69.compute-1.amazonaws.com:25179' REDIS_URL
        },
        # "ROUTING": "chat.routing.websocket_urlpatterns", #todo: add "ROUTING": "chat.routing.websocket_urlpatterns",
    },
}

CACHES = {
    "default": {
         "BACKEND": "redis_cache.RedisCache",
         "LOCATION": os.environ.get('REDIS_TLS_URL'),
         "OPTIONS": {
            "CONNECTION_POOL_KWARGS": {
                "ssl_cert_reqs": False
            }
        }
    }
}


DATABASES = { # Use this to use local test DB
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

DB_URL = os.environ['DATABASE_URL']
DATABASE_URL = DB_URL


AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'America/Los_Angeles'
USE_I18N = True
USE_L10N = True
USE_TZ = True

STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATIC_URL = '/static/'

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

CRISPY_TEMPLATE_PACK = 'bootstrap4'

LOGIN_REDIRECT_URL = 'blog-home'
LOGIN_URL = 'login'

django_heroku.settings(locals())

DATA_UPLOAD_MAX_NUMBER_FIELDS = 4000
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'staticfilescustom') #todo: may have to add own staticFileDir folder
]

django.setup()

blog/models (which is where the error is complaining about):

from django.db import models
from django.utils import timezone


from django.contrib.auth import get_user_model
User = get_user_model()
from django.conf import settings



from django.urls import reverse
from datetime import datetime, timedelta

# we're inheriting from the models.Model
class Post(models.Model):
    title = models.CharField(max_length=100) # character field
    content = models.TextField() # Unrestricted text
    date_posted = models.DateTimeField(default=timezone.now)
    last_modified = models.DateTimeField(auto_now=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE)

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('post-detail', kwargs={'pk': self.pk})


class Game(models.Model):
    name = models.TextField()  # Unrestricted text
    platform = models.CharField(max_length=100)  # character field
    created_date = models.DateTimeField(default=timezone.now)
    name_and_platform = models.TextField(default='N/A') #todo: find a good max char limit
    author = models.ForeignKey(User, on_delete=models.CASCADE)

    def __str__(self):
        return self.name  # return game name when game.objects.all() is called

    def get_name_and_platform(self):
        return ''.join([self.name, '(', self.platform, ')'])

    def save(self, *args, **kwargs):
        self.name_and_platform = self.get_name_and_platform()
        super(Game, self).save(*args, **kwargs)




class Trade(models.Model):
    name = models.TextField() # Unrestricted text
    created_date = models.DateTimeField(default=timezone.now)
    is_trade_proposed = models.BooleanField(default=False) 
    user_who_posted = models.ForeignKey(User, on_delete=models.CASCADE)
    owned_game = models.ForeignKey(Game, on_delete=models.CASCADE, related_name='owned_game', db_column='owned_game')
    desired_game = models.ForeignKey(Game, on_delete=models.CASCADE, related_name='desired_game', db_column='desired_game')

    def get_trade_name(self):
        return ''.join([self.user_who_posted.username, '(', timezone.now().strftime("%b %d, %Y %H:%M:%S UTC"), ')'])

    def save(self, *args, **kwargs):
        self.name = self.get_trade_name()
        super(Trade, self).save(*args, **kwargs)

    def __str__(self):
        return self.name # return game name when game.objects.all() is called


class Transaction(models.Model):
    name = models.TextField() # Unrestricted text
    created_date = models.DateTimeField(default=timezone.now)
    trade_one = models.ForeignKey(Trade, on_delete=models.CASCADE, related_name='trade_one', db_column='trade_one')
    trade_two = models.ForeignKey(Trade, on_delete=models.CASCADE, related_name='trade_two', db_column='trade_two')
    status = models.TextField()  # Unrestricted text. Validated in form.
    expiry_date = models.DateTimeField(default=datetime.today() + timedelta(days=3))
    open_expiry_date = models.DateTimeField(default=datetime.today() + timedelta(days=9))
    user_cancelled_date = models.DateTimeField(null=True, blank=True)

    def get_transaction_name(self):
        return ''.join([str(self.trade_one_id), ' and ', str(self.trade_two_id), ' on ', timezone.now().strftime("%b %d, %Y %H:%M:%S UTC"), ''])

    def get_status_on_insert(self):
        return 'Waiting for 2nd confirmation from ' + str(self.trade_two.user_who_posted)

    def save(self, *args, **kwargs):
        if self.name == '':
            self.name  = self.get_transaction_name()
        if self.status == '':
            self.status = self.get_status_on_insert()
        super(Transaction, self).save(*args, **kwargs)

    def __str__(self):
        return self.name # return name when game.objects.all() is called

    def get_absolute_url(self): #todo: remove?
        return reverse('confirmed-trade', kwargs={'pk': self.pk})

chat/models.py

from django.db import models
from django.contrib.auth import get_user_model
User = get_user_model()
# from django.conf import settings
from django.utils import timezone


class Message(models.Model):
    author = models.ForeignKey(User, related_name='author_messages', on_delete=models.CASCADE)
    content = models.TextField()
    timestamp = models.DateTimeField(default=timezone.now) #todo: timezone fix?

    def __str__(self):
        return self.author.username

    def last_10_messages(self):
        return Message.objects.order_by('-timestamp').all()[:10] # only load last x msgs from DB

users/models.py

from django.db import models
from PIL import Image
from django.contrib.auth import get_user_model
User = get_user_model()
from django.conf import settings



class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    image = models.ImageField(default='default.jpg', upload_to='profile_pics')

    def __str__(self):
        return f'{self.user.username} Profile'

chat/consumers.py

# chat/consumers.py
import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
from .models import Message


from django.contrib.auth import get_user_model
User = get_user_model()
from django.conf import settings


class ChatConsumer(WebsocketConsumer):

    def fetch_messages(self, data):
        print('~~~fetch_messages~~~')
        messages = Message.last_10_messages(self)
        content = {
            'command': 'messages',
            'messages': self.messages_to_json(messages)
        }
        print('content: ' + str(content))
        self.send_message(content)


    def new_message(self, data):
        print('~~~in new_message: 2~~~')
        author = data['from']
        print('author: ' + author)
        author_user = User.objects.filter(username=author)[0]
        message = Message.objects.create(author=author_user, content=data['message'])
        content = {
            'command': 'new_message',
            'message': self.message_to_json(message)
        }
        return self.send_chat_message(content)


    def messages_to_json(self, messages):
        print('~~~messages_to_json~~~')
        result = []

        for message in messages:
            result.append(self.message_to_json(message))
        return result


    def message_to_json(self, message):
        print('~~~message_to_json~~~')
        return {
            'author': message.author.username,
            'content': message.content,
            'timestamp': str(message.timestamp)
        }

    commands = {
        'fetch_messages': fetch_messages,
        'new_message': new_message
    }


    def connect(self):
        print('~~~connect~~~')
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.room_group_name = 'chat_%s' % self.room_name

        # Join room group
        async_to_sync(self.channel_layer.group_add)(
            self.room_group_name,
            self.channel_name
        )

        self.accept()

    def disconnect(self, close_code):
        print('~~~disconnect~~~')
        # Leave room group
        async_to_sync(self.channel_layer.group_discard)(
            self.room_group_name,
            self.channel_name
        )

    # Receive message from WebSocket
    def receive(self, text_data):
        print('~~~receive: 1~~~')
        data = json.loads(text_data)
        self.commands[data['command']](self, data) # either fetch_messages or new_message

    def send_chat_message(self, message):
        print('in send_chat_message: 3')
        async_to_sync(self.channel_layer.group_send)( # Send message to room group
            self.room_group_name,
            {
                'type': 'chat_message',
                'message': message
            }
        )

    def send_message(self, message):
        self.send(text_data=json.dumps(message))

    # Receive message from room group
    def chat_message(self, event):

        message = event['message']
        print('message: ' + str(message))
        content1 = message['message']
        print('content1: ' + str(content1))
        content2 = content1['content']
        print('content2: ' + content2)
        self.send(text_data=json.dumps(message)) # Send message to WebSocket

~~~~~~~~~UPDATE:~~~~~~~~~ When i switched my procfile from

release: python manage.py migrate
web: daphne django_project.asgi:application --port $PORST --bind 0.0.0.0 v2
worker: python manage.py runworker channel_layer --settings=django_project.settigns -v2

to

web: gunicorn django_project.wsgi

my application successfully built! Why would my procfile be resulting in that error?

Answer

It seems like the problem is related to the way your Procfile was configured to run the web server. Specifically, you were using daphne to run your ASGI application, which caused issues with your app's initialization, leading to the AUTH_USER_MODEL error. By switching to gunicorn with wsgi, your application built successfully.

Here's why it happened:

  1. ASGI and WSGI Mismatch:

    • Django supports both ASGI and WSGI, but they are used for different types of applications. ASGI (Asynchronous Server Gateway Interface) is designed for handling real-time features like WebSockets, long-lived connections, etc., while WSGI (Web Server Gateway Interface) is for more traditional HTTP applications.
    • daphne is an ASGI server used to handle ASGI applications. However, in your case, the error message suggests that the Django app could not fully initialize due to some issue with your application setup when using daphne.
    • When you switched to gunicorn with wsgi, it was able to work because gunicorn is a WSGI server, and it loads the Django app in a traditional way, without the complexities of handling WebSockets or ASGI routing. It appears your application relies on standard HTTP requests and doesn't need real-time features provided by ASGI.
  2. AUTH_USER_MODEL Issue:

    • The AUTH_USER_MODEL error happens when the AUTH_USER_MODEL setting in Django's settings.py points to a user model that has not been installed or loaded correctly.
    • It's possible that daphne (which is more complex due to its ASGI nature) did not properly initialize the Django settings or database connections, preventing the AUTH_USER_MODEL from being set correctly.
    • gunicorn with wsgi would have avoided this issue because it initializes the application in a simpler, more predictable manner.

Why daphne failed:

  • The issue could be that you may not have configured everything needed for daphne properly in your project or Procfile. daphne is often used with channels for real-time communications and might not handle some of the default Django startup behavior as expected.
  • It might have skipped the necessary startup sequence for the database and user models, which causes AUTH_USER_MODEL to refer to a model that Django doesn't recognize as installed.

Solution:

  • Using Gunicorn is a safe choice if you're not explicitly using ASGI features like WebSockets or long-polling. It works well with standard Django applications, and your application seems to be fine with it.
  • If you do need ASGI support (e.g., for WebSockets), then you should revisit your ASGI configuration and ensure that it properly initializes Django and handles the required settings. The error you encountered may also have been related to an incomplete or incorrect ASGI application configuration in django_project.asgi.py.

Conclusion:

Switching to gunicorn works because it uses a WSGI-based approach and properly initializes the Django application. If you need to use daphne for ASGI (for real-time features like WebSockets), you'll need to ensure that your configuration (especially in django_project.asgi.py) is correct, and that all dependencies are properly initialized.