I have a problem and I will do the best to explain it, to see if you can help me out.
CONTEXT
I have a system running in a test server, which is set up like a production environment in order to test my code before merging to master and give the go to the production server provider of my client to update the code. This means it runs in DEBUG = False
. All good, all perfect for months.
I decided to activate the ManifestStaticFilesStorage
setting in order to have a hash number added in my static files, I've used it before and it's a good way to break cache rules when updating files (like CSS rules that refuse to load). There is an issue with cache that may be solvable messing around with the server but that's not an option in this case.
Everything went smoothly:
BUT...
THE PROBLEM
This system manages content (images, audio files and custom fonts). When I activated the ManifestStaticFilesStorage
setting, all uploaded files started to throw 404 errors (and some occasional 500 error) in the server access log. Meaning, they look like this:
You can see the broken image icon but you can also see the background colores of each square (color which is injected by JS because it can be customized in the custom CMS). These images are uploaded in the CMS and they live in the media folder configured in the settings file.
Of course, if I go to DEBUG = True
, everything gets fixed (come on. -.-), I went and recreated production enviroment in local: same issue: DEBUG = False
bad, DEBUG = True
works
THOUGHTS
DEBUG = True
would failSo, I ran out of ideas. Maybe someone out there has the answer, I hope so. I will still be trying to solve it but I can use the help, please.
RELEVANT CODE
storage.py
class ManifestStaticFilesStorageNotStrict(ManifestStaticFilesStorage):
"""A relaxed implementation of django's ManifestStaticFilesStorage.
"""
manifest_strict = False
settings.py (redacted)
# -*- coding: utf-8 -*-
# Standard libs imports
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
# Django libs imports
from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = <SECRET KEY HERE (: >
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['*']
##########################
# APPLICATION DEFINITION #
##########################
DJANGO_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.sitemaps',
'django.contrib.staticfiles',
]
THIRD_PARTY_APPS = [
# https://github.com/audiolion/django-behaviors
'behaviors.apps.BehaviorsConfig',
# https://github.com/zostera/django-bootstrap4
'bootstrap4',
# https://django-ckeditor.readthedocs.io/en/latest/
'ckeditor',
# https://github.com/praekelt/django-recaptcha
'captcha',
# https://django-tables2.readthedocs.io/en/latest/index.html
'django_tables2',
# https://github.com/django-extensions/django-extensions
'django_extensions',
]
CUSTOM_APPS = [
<CUSTOM APPS HERE (: >
]
ELASTICSEARCH_DSL = {
<ELASTICSEARCH DATA HERE (: >
}
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + CUSTOM_APPS
SITE_ID = 1
AUTH_USER_MODEL = 'users.User'
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',
# https://github.com/PaesslerAG/django-currentuser
'django_currentuser.middleware.ThreadLocalUserMiddleware',
]
ROOT_URLCONF = 'main.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'main/templates')],
'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',
'main.context_processors.add_to_context',
],
},
},
]
DJANGO_TABLES2_TEMPLATE = 'django_tables2/bootstrap-responsive.html'
WSGI_APPLICATION = 'main.wsgi.application'
LOGIN_URL = reverse_lazy('back_office:auth:login')
SILENCED_SYSTEM_CHECKS = ['captcha.recaptcha_test_key_error']
#####################
# DATABASE SETTINGS #
#####################
#
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
DATABASES = {
<DATABASE DATA HERE (: >
}
##########################
# AUTHENTICATION BACKEND #
##########################
#
# https://docs.djangoproject.com/en/2.0/topics/auth/customizing/
AUTHENTICATION_BACKENDS = [
<AUTHENTICATION_BACKENDS DATA HERE (: >
]
################################
# PASSWORD VALIDATION SETTINGS #
################################
#
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation'
'.UserAttributeSimilarityValidator',
},
# {
# 'NAME': 'django.contrib.auth.password_validation'
# '.MinimumLengthValidator',
# },
{
'NAME': 'main.validators.password_validators'
'.CustomMinimumLengthValidator',
},
# {
# 'NAME': 'django.contrib.auth.password_validation'
# '.CommonPasswordValidator',
# },
{
'NAME': 'main.validators.password_validators'
'.CustomCommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation'
'.NumericPasswordValidator',
},
]
##################
# GOOGLE ANALYTICS
##################
GOOGLE_ANALYTICS_ID = ""
##################
# EMAIL SETTINGS #
##################
<SETTINGS HERE (: >
#################################
# INTERNATIONALIZATION SETTINGS #
#################################
#
# https://docs.djangoproject.com/en/1.11/topics/i18n/
LANGUAGE_CODE = 'es'
LANGUAGES = (
('en', _('English')),
('es', _('Spanish')),
)
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
LOCALE_PATHS = (
os.path.join(BASE_DIR, 'locale'),
)
#############################
# STATIC FILES SETTINGS #
# (CSS, JavaScript, Images) #
#############################
#
# https://docs.djangoproject.com/en/1.11/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'static/media')
##############
# RECAPTCHA #
##############
<MORE SETTINGS HERE (: >
################
# FILE STORAGE #
################
#
# This settings is for a custom random number to add to all uploaded
# files in order to break cache
DEFAULT_FILE_STORAGE = 'main.storage.CustomFileSystemStorage'
##################
# LOCAL SETTINGS #
##################
#
# This is the file that contains local configurations like DB passwords,
# keys, user for an API, etc.
#
# This import is done at the end because it will override the default settings
# stablish here.
try:
from .local_settings import * # noqa
except Exception as e:
pass
###################
# CKEDITOR CONFIG #
###################
CKEDITOR_CONFIGS = {
'default': {
# 'skin': 'moono',
'toolbar': 'full',
'skin': 'office2013',
'width': '100%',
}
}
########################
# STATIC FILES STORAGE #
########################
STATICFILES_STORAGE = \
'main.storage.ManifestStaticFilesStorageNotStrict'
local_settings.py (redacted)
# Production settings
DEBUG = False
###################
# ALLOWED DOMAINS #
###################
ALLOWED_HOSTS = [
'localhost',
'localhost:8000',
'127.0.0.1',
'127.0.0.1:8000',
<DOMAIN DATA HERE (: >
]
#############
# DATABASES #
#############
DATABASES = {
<DATABASE DATA HERE (: >
}
################
# STATIC FILES #
################
STATIC_ROOT = '<PATH TO SERVER STATIC FOLDER>'
MEDIA_ROOT = '<PATH TO SERVER STATIC FOLDER>/media'
Thanks in advance.
Well, after hitting my head to the wall a lot, a good night sleep and fresh ideas, I found the problem. It was the silliest thing ever, as usual.
If you can see in the settings.py , the MEDIA_URL
is set to /media/
. Normally, this wouldn't be a problem in an Apache server but in WebFaction, it turned out to be the culprit of my headache.
For those who don't know, WebFaction obliges you to create apps for everything: Python env, PHP env, Static env, WordPress, Joomla, etc. By creating an app, you have to assing a unique path inside the Website definition, which is a set of apps running under a given domain, so all apps have paths under the same domain and you don't have to modify (in most cases) the httpd.conf
file.
I have an app for the Django code and another app for the static files, under the domain static/
. In the local_settings.py
, you can see that the MEDIA_ROOT
is inside this static path.
In a regular Apache deploy, you just assign the /media/
URL to the path and that's it. In WebFaction, given that there is no static app for using the media/
, the MEDIA_URL
variable has to have static/media
as assigned value when switching to DEBUG = False
.
Just to remember: DEBUG = False
make Django stop serving the static files and leave that entirely to the server where is deployed. So, in DEBUG = True
, it didn't matter which was the media URL because Django is smart enough to see past that. But when I made the switch to DEBUG = False
, it naturally failed.
Silly me, but well, this is how we learn.
Regards.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.