My Django project uses Docker, gunicorn, and whitenoise. I recently altered settings to prepare for deployment, most notably adding configurations for AWS-hosted media files. Now when I run the project locally and collectstatic
the static files do not update in the browser. I do, however, see the changes where static files are collected.
Things I've tried/double checked:
- Ensuring I have a shared volume between the container and where static files are kept/updated
- Adding a step to
collectstatic
in my Dockerfile - Confirming my static files settings
Is something about django-storages
causing the issue? I thought that previously I was able to make SCSS changes and have them show up by refreshing the browser. But that's not happening. Even running collectstatic
inside the container has no effect.
# relevant settings.py
INSTALLED_APPS = [
...
"whitenoise.runserver_nostatic",
"storages",
...
]
MIDDLEWARE = [
"django.middleware.cache.UpdateCacheMiddleware",
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
...
]
# AWS config (use in production only)
USE_S3 = env.bool("USE_S3", default=not DEBUG)
if USE_S3:
AWS_ACCESS_KEY_ID = env.str("AWS_ACCESS_KEY_ID", default="")
AWS_SECRET_ACCESS_KEY = env.str("AWS_SECRET_ACCESS_KEY", default="")
AWS_STORAGE_BUCKET_NAME = env.str("AWS_STORAGE_BUCKET_NAME", default="")
AWS_DEFAULT_ACL = None
AWS_S3_REGION_NAME = env.str("AWS_S3_REGION_NAME", default="us-east-2")
AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
AWS_S3_OBJECT_PARAMETERS = {
'CacheControl': 'max-age=86400',
}
# S3 Public Media Settings
PUBLIC_MEDIA_LOCATION = 'media'
MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{PUBLIC_MEDIA_LOCATION}/'
# S3 Private Media Settings
DEFAULT_FILE_STORAGE = 'config.storage_backends.PrivateMediaStorage'
PRIVATE_MEDIA_LOCATION = 'private'
PRIVATE_FILE_STORAGE = 'config.storage_backends.PrivateMediaStorage'
else:
MEDIA_URL = "/media/"
MEDIA_ROOT = str(BASE_DIR.joinpath("media"))
STATIC_ROOT = str(BASE_DIR.joinpath("staticfiles"))
STATIC_URL = '/static/'
STATICFILES_STORAGE = 'pipeline.storage.PipelineStorage'
DEBUG_PROPAGATE_EXCEPTIONS = True
STATICFILES_FINDERS = (
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
"pipeline.finders.PipelineFinder",
)
PIPELINE_YUGLIFY_BINARY = "/usr/local/bin/node"
PIPELINE = {
"CSS_COMPRESSOR": "pipeline.compressors.yuglify.YuglifyCompressor",
"COMPILERS": (
"pipeline.compilers.sass.SASSCompiler",
"config.compilers.RollupCompiler",
),
"YUGLIFY_BINARY": str(BASE_DIR.joinpath("node_modules/.bin", "yuglify")),
"SASS_BINARY": str(BASE_DIR.joinpath("node_modules/.bin", "sass")),
"STYLESHEETS": {
"twirlmate": {
"source_filenames": ("pages/scss/styles.scss",),
"output_filename": "css/styles.css",
}
},
"JS_COMPRESSOR": "pipeline.compressors.NoopCompressor", # TODO: Update this to jsmin or something better.
"ROLLUP_BINARY": str(BASE_DIR.joinpath("node_modules/.bin", "rollup")),
"ROLLUP_ARGUMENTS": ["--config", "--configDebug", "--format", "iife"],
"DISABLE_WRAPPER": True,
"JAVASCRIPT": {
"twirlmate": {
"source_filenames": (
"pages/js/core.js",
"pages/js/file-field.js",
"pages/js/lazy-loading.js",
"pages/js/modal.js",
"pages/js/navigation.js",
"pages/js/notifications.js",
),
"output_filename": "js/twirlmate.js",
},
"components": {
"source_filenames": ("pages/js/vue/components.rollup.js",),
"output_filename": "js/components.js",
},
},
}
#docker-compose.yml
version: '3.8'
services:
web:
build: .
command: gunicorn config.wsgi -b 0.0.0.0:8000
volumes:
- .:/code
ports:
- 8000:8000
depends_on:
- db
environment:
- "DJANGO_SECRET_KEY=****"
- "DJANGO_DEBUG=True"
- "DJANGO_SECURE_SSL_REDIRECT=False"
- "SECURE_HSTS_SECONDS=0"
- "SECURE_HSTS_INCLUDE_SUBDOMAINS=False"
- "SECURE_HSTS_PRELOAD=False"
- "SESSION_COOKIE_SECURE=False"
- "CSRF_COOKIE_SECURE=False"
db:
image: postgres:11
volumes:
- postgres_data:/var/lib/postgresql/data/
environment:
- "POSTGRES_HOST_AUTH_METHOD=trust"
volumes:
postgres_data:
# Dockerfile
# Pull base image
FROM python:3.8
# Set environment variables and build arguments
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash -
RUN apt-get install -y nodejs build-essential
# Set working directory
WORKDIR /code
RUN npm install sass --also=dev
RUN npm install yuglify --also=dev
RUN npm install
# Install dependencies
COPY Pipfile Pipfile.lock /code/
# Figure out conditional installation of dev dependencies
# Will need to remove --dev flag for production
RUN pip install pipenv && pipenv install --system --dev
COPY . /code/
# Collect static files
RUN python manage.py collectstatic --noinput
Project Structure
twirlmate
├── .babelrc
├── .env
├── .gitignore
├── .pylintrc
├── Dockerfile
├── Makefile
├── Pipfile
├── Pipfile.lock
├── config
│ ├── __init__.py
│ ├── asgi.py
│ ├── compilers.py
│ ├── compressors.py
│ ├── constants.py
│ ├── settings.py
│ ├── storage_backends.py
│ ├── urls.py
│ └── wsgi.py
├── docker-compose.yml
├── heroku.yml
├── manage.py
├── package-lock.json
├── package.json
├── pages
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── static
│ │ └── pages
│ │ ├── css
│ │ │ ├── styles.css
│ │ │ └── styles.css.map
│ │ ├── fonts
│ │ ├── images
│ │ ├── js
│ │ │ └── vue
│ │ │ ├── components
│ │ │ │ ├── cards
│ │ │ │ ├── inputs
│ │ │ │ ├── miniMenus
│ │ │ │ └── modals
│ │ │ ├── components.rollup.js
│ │ │ ├── data
│ │ │ ├── directives
│ │ │ ├── forms
│ │ │ ├── mixins
│ │ │ ├── services
│ │ │ ├── views
│ │ │ └── vue-store.js
│ │ └── scss
│ │ ├── 01-utilities
│ │ ├── 02-vendors
│ │ ├── 03-base
│ │ ├── 04-layout
│ │ ├── 05-components
│ │ ├── 06-pages
│ │ ├── styles.css
│ │ ├── styles.css.map
│ │ └── styles.scss
│ ├── templatetags
│ │ ├── __init__.py
│ │ ├── __pycache__
│ │ ├── css_classes.py
│ │ └── url_paths.py
│ ├── tests
│ │ ├── __init__.py
│ │ └── test_views.py
│ ├── urls.py
│ └── views.py
├── pyproject.toml
├── rollup.config.js
├── staticfiles
│ ├── admin
│ ├── debug_toolbar
│ ├── django_extensions
│ ├── pages
│ │ ├── css
│ │ │ ├── styles.css
│ │ │ └── styles.css.map
│ │ ├── fonts
│ │ ├── images
│ │ ├── js
│ │ │ └── vue
│ │ │ ├── components
│ │ │ │ ├── cards
│ │ │ │ ├── inputs
│ │ │ │ ├── miniMenus
│ │ │ │ └── modals
│ │ │ ├── components.rollup.js
│ │ │ ├── components.rollup.roll.js
│ │ │ ├── data
│ │ │ ├── directives
│ │ │ ├── forms
│ │ │ ├── mixins
│ │ │ ├── views
│ │ │ └── vue-store.js
│ │ └── scss
│ │ ├── 01-utilities
│ │ ├── 02-vendors
│ │ ├── 03-base
│ │ ├── 04-layout
│ │ ├── 05-components
│ │ ├── 06-pages
│ │ ├── styles.css
│ │ ├── styles.css.map
│ │ └── styles.scss
│ └── rest_framework
└── templates
├── 403.html
├── _base.html
├── account
│ ├── login.html
│ ├── password_change.html
│ ├── password_change_done.html
│ ├── password_reset.html
│ ├── password_reset_done.html
│ ├── password_reset_from_key.html
│ ├── password_reset_from_key_done.html
│ └── signup.html
├── emails
│ ├── duet_invitation.html
│ └── duet_invitation.txt
├── includes
│ ├── _favicons.html
│ ├── _footer.html
│ ├── _header.html
│ └── forms
│ ├── _widgets.html
│ └── attrs.html
├── index.html
├── pages
│ ├── for_coaches.html
│ ├── for_directors.html
│ └── home.html
├── registration
│ ├── account-settings.password-change-done.html
│ ├── account-settings.password-change.html
│ ├── account-settings.profile-list.html
│ └── privacy-policy.html
└── studios
├── _studio_admin_base.html
├── my_studios.html
├── staff_list.html
├── studio_create.html
├── studio_detail.admin.html
├── studio_detail.html
├── studio_join.html
├── studio_list.html
├── studio_update.html
└── team_list.html
index.html, from which all other templates inherit
{% load static pipeline %}
<!doctype html>
<html class="no-js" lang="en">
<head>
... other various meta tags ...
{% stylesheet 'twirlmate' %}
<!-- the above produces the path: /static/pages/scss/styles.css -->
{% block styles %}{% endblock %}
{% block extrastyles %}{% endblock %}
</head>
<body class="{% block body-class %}{% endblock %}" {% block body-attributes %}{% endblock %}>
<div class="main-wrapper" data-target="main-wrapper">
<a class="skip-to-main" href="#main">Skip to main content</a>
{% block header %}{% endblock header %}
{% block notifications %}{% endblock notifications %}
{% block main %}{% endblock main %}
{% block footer %}{% endblock footer %}
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
{% javascript 'twirlmate' %}
{% javascript 'components' %}
{% block scripts %}{% endblock scripts %}
{% block extrascripts %}{% endblock extrascripts %}
</body>
</html>
SOLVED
It turns out there was a duplicate, stale file with the same name, and the same path. The template was looking for /static/pages/scss/styles.css
, which existed in its updated form in my root static directory, the correct spot. But there was also a file in pages/static/pages/scss/styles.css
that must've gotten generated during previous changes to settings.py
. Somehow I guess that file was getting repeatedly pulled in and overwriting the updated version. All was fixed once I deleted the stale file.