0

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.

Nathan_James
  • 139
  • 2
  • 9
  • Are you using {% static %} tag in your templates? Have you cleared the browser cache? – periwinkle Mar 19 '21 at 13:04
  • Thanks @periwinkle, yes. I'm using django-pipeline, so in my templates I'm inserting my bundle with {% stylesheet 'twirlmate' %}. I've added the pipeline config above for more information if it's helpful. And yes I've done a hard refresh in the browser. – Nathan_James Mar 19 '21 at 13:15
  • Can you post your html for one of the templates and project structure that shows where your static files are? I had similar problems, once it was wrong path to the css and another time it was the browser (hard refresh didn’t work, had to clear the cache). Judging by your my bet is on the incorrect path to the static files – periwinkle Mar 19 '21 at 18:22
  • Thanks @periwinkle, I've added the project structure and example template above. I just checked, and the file to which the stylesheet link is pointing has the updated styles in my code base. But not in the browser. I just manually cleared the cache, and nothing changed. – Nathan_James Mar 19 '21 at 20:30
  • I also tried deleting the staticfiles dir again and rebuilding the image, including a run of collectstatic. No change. – Nathan_James Mar 19 '21 at 20:36
  • So it seems like updated css is not showing in the browser, that's progress. Does updated css show up in `staticfiles` folder after you run collectstatic? – periwinkle Mar 20 '21 at 01:03
  • It turns out there was a 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. Thank you again for your thoughts @periwinkle. – Nathan_James Mar 21 '21 at 01:55
  • good luck with your app! – periwinkle Mar 21 '21 at 02:19
  • My issue was browser caching. God I hate browser caching and I don't care how it much it might speed things up. – Harlin May 12 '23 at 21:41

0 Answers0