0

I'm trying to add a Django backend and a react frontend to Heroku. Following this tutorial. I'm using whitenoise for serving static files.

When runnning python manage.py collectstatic I keep getting the same error:

django.core.exceptions.SuspiciousFileOperation: The joined path (C:\Users\<name>\Documents\combined_connect\static\media\background-pattern.f6347303.png) is located outside of the base path component (C:\Users\<name>\Documents\combined_connect\staticfiles)

This command is also run by heroku python hooks when deploying. The python hook runs after the react hook, so the react build folder is definitely there. For testing purposes I also ran build locally for when I call the collectstatic command.

The problem is something to do with how I'm defining static roots, but for the life of me I can't figure it out. I've used multiple tutorials, the django docs on static files, and the heroku dev docs on static files.

Why is it looking for my media files (background-pattern.png etc.) in static? On collectstatic everything gets put into the staticfiles directory. No matter what I change the file names or variables to, that error about the lookup conflict between staticfiles and static always happens.

Here's how I'm defining them:

settings.py

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

STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
​

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'build')], # pointing to the React build folder
        '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',
            ],
        },
    },
]

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

Here's how the project is structured:

enter image description here

EDIT: Using this config, I can serve the backend just fine. If I go to the root url /, I see the Django 404 page instead o my React homepage. Going to /admin works perfectly, and I can call /api/ endpoints.

crevulus
  • 1,658
  • 12
  • 42
  • Here is an answer for the same question like yours - https://stackoverflow.com/questions/37241902/django-joined-path-is-located-outside-of-the-base-path-component-static-img – thelovekesh May 10 '21 at 16:48

1 Answers1

3

First maybe some clarification :-)

The "Media" directory is used for content uploaded for example by a user. So it's more a dynamic content, not static. Or at least not static with every instance of your service. Therefore it's usually also not shipped with your application. Details in the django documentation

Also check settings documentation about MEDIA_ROOT

I assume in your settings you something like MEDIA_ROOT = "build/static/media/" or something similar, which results in the conflict.

If you want to use "media" files, hence user uploaded files, you need a different storage on Heroku anyway. Heroku Dynos have a ephemeral filesystem which means you get a fresh copy of your instance once a day, which results in loss of all that data you stored during the life-time of the instance.

To solve this this problem you can store those media files for example on AWS S3. Django has an awesome addon, which replaces the filesystem storage with AWS S3 storage. Which means the API from your point of you as a Developer does not change at all.

Here is a Django Development and PROD setting example with django-s3-storage:

Development example config (nothing special, no S3):

class Development(Configuration):

    ... # Your code

    @property
    def MEDIA_ROOT(self):
        default_value = os.path.join(self.BASE_DIR, 'public', 'media')
        return values.Value(default=default_value, environ_name='MEDIA_ROOT')


    @property
    def STATIC_ROOT(self):
        default_value = os.path.join(self.BASE_DIR, 'static')
        return values.Value(default=default_value, environ_name='STATIC_ROOT')

    @property
    def STATICFILES_DIRS(self):
        return (
            os.path.join(self.BASE_DIR, 'whatever', 'react', 'build'),
            os.path.join(self.BASE_DIR, 'yourapp', 'admin', 'static'),
        )
    ... # Your code

For your local development make SURE media and static do not point to the same directory/structure.

Production example config:

# Inheriting from Development only serves as example. Good practice would be to move code used by both environments into a `Base` class. 
class Production(Development):
    ... # Your code

    DEFAULT_FILE_STORAGE = 'django_s3_storage.storage.S3Storage'

    AWS_REGION = "us-east-1"

    AWS_ACCESS_KEY_ID = values.Value(default='', environ_name='AWS_ACCESS_KEY_ID')
    AWS_SECRET_ACCESS_KEY = values.Value(default='', environ_name='AWS_SECRET_ACCESS_KEY')
    AWS_S3_BUCKET_NAME = values.Value(default='', environ_name='AWS_S3_BUCKET_NAME')

    @property
    def INSTALLED_APPS(self):
        return super().INSTALLED_APPS + [
            'django_s3_storage',
        ]

    ... # Your code
Mathias
  • 6,777
  • 2
  • 20
  • 32
  • Thanks for your detailed explanation. Will only have time to test this on the weekend, but will be sure to test it before the bounty expires. – crevulus May 11 '21 at 09:11
  • No rush... I don't care about the bounty. It just happened that I deployed a project on Heroku using Django/React some time ago and faced similar issues with media / static files. – Mathias May 11 '21 at 14:19
  • Didn't get a chance to test this in the end, but it's a very detailed answer that I'm sure will prove very useful to anyone who finds their way here. Marked it as correct as others have also upvoted it. – crevulus May 17 '21 at 15:14