8
votes

I've been through the ringer getting my Django app setup on Heroku using Amazon s3 to host the static and media files. I've been following this guide https://www.caktusgroup.com/blog/2014/11/10/Using-Amazon-S3-to-store-your-Django-sites-static-and-media-files/ and what seems like thousands of other resources, collectstatic has worked and heroku is deploying it - but displays a 400 error. When I try and run it locally i get more info:

Attempted access to '/css/reset.css' denied.

This is the line that gets highlighted:

<link rel="stylesheet" type="text/css" href="{% static '/css/reset.css' %}">

I can load the static files direct from the URL if I grab it from my s3 admin panel so I figured it wasn't a bucket policy issue, I've messed around with https / http options but no joy. So I figure it must be the wrong path is being called somehow in the code - i just can't see where!

Any help much appreciated, I don't think I've blinked for about 4 hours straight.

Traceback:

File "/home/devtop/webdev/projects/intro/myvenv/lib/python3.5/site-packages/storages/backends/s3boto.py" in _normalize_name
  358.             return safe_join(self.location, name)

File "/home/devtop/webdev/projects/intro/myvenv/lib/python3.5/site-packages/storages/backends/s3boto.py" in safe_join
  59.         raise ValueError('the joined path is located outside of the base path'

During handling of the above exception (the joined path is located outside of the base path component), another exception occurred:

File "/home/devtop/webdev/projects/intro/myvenv/lib/python3.5/site-packages/django/core/handlers/exception.py" in inner
  41.             response = get_response(request)

File "/home/devtop/webdev/projects/intro/myvenv/lib/python3.5/site-packages/django/core/handlers/base.py" in _get_response
  187.                 response = self.process_exception_by_middleware(e, request)

File "/home/devtop/webdev/projects/intro/myvenv/lib/python3.5/site-packages/django/core/handlers/base.py" in _get_response
  185.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

File "/home/devtop/webdev/projects/intro/intro/profiles/views.py" in index
  14.     return render(request, 'home.html', {'welcome':welcome})

File "/home/devtop/webdev/projects/intro/myvenv/lib/python3.5/site-packages/django/shortcuts.py" in render
  30.     content = loader.render_to_string(template_name, context, request, using=using)

File "/home/devtop/webdev/projects/intro/myvenv/lib/python3.5/site-packages/django/template/loader.py" in render_to_string
  68.     return template.render(context, request)

File "/home/devtop/webdev/projects/intro/myvenv/lib/python3.5/site-packages/django/template/backends/django.py" in render
  66.             return self.template.render(context)

File "/home/devtop/webdev/projects/intro/myvenv/lib/python3.5/site-packages/django/template/base.py" in render
  207.                     return self._render(context)

File "/home/devtop/webdev/projects/intro/myvenv/lib/python3.5/site-packages/django/template/base.py" in _render
  199.         return self.nodelist.render(context)

File "/home/devtop/webdev/projects/intro/myvenv/lib/python3.5/site-packages/django/template/base.py" in render
  990.                 bit = node.render_annotated(context)

File "/home/devtop/webdev/projects/intro/myvenv/lib/python3.5/site-packages/django/template/base.py" in render_annotated
  957.             return self.render(context)

File "/home/devtop/webdev/projects/intro/myvenv/lib/python3.5/site-packages/django/template/loader_tags.py" in render
  177.             return compiled_parent._render(context)

File "/home/devtop/webdev/projects/intro/myvenv/lib/python3.5/site-packages/django/template/base.py" in _render
  199.         return self.nodelist.render(context)

File "/home/devtop/webdev/projects/intro/myvenv/lib/python3.5/site-packages/django/template/base.py" in render
  990.                 bit = node.render_annotated(context)

File "/home/devtop/webdev/projects/intro/myvenv/lib/python3.5/site-packages/django/template/base.py" in render_annotated
  957.             return self.render(context)

File "/home/devtop/webdev/projects/intro/myvenv/lib/python3.5/site-packages/django/templatetags/static.py" in render
  105.         url = self.url(context)

File "/home/devtop/webdev/projects/intro/myvenv/lib/python3.5/site-packages/django/templatetags/static.py" in url
  102.         return self.handle_simple(path)

File "/home/devtop/webdev/projects/intro/myvenv/lib/python3.5/site-packages/django/templatetags/static.py" in handle_simple
  117.             return staticfiles_storage.url(path)

File "/home/devtop/webdev/projects/intro/myvenv/lib/python3.5/site-packages/storages/backends/s3boto.py" in url
  487.         name = self._normalize_name(self._clean_name(name))

File "/home/devtop/webdev/projects/intro/myvenv/lib/python3.5/site-packages/storages/backends/s3boto.py" in _normalize_name
  361.                                       name)

Exception Type: SuspiciousOperation at /
Exception Value: Attempted access to '/css/reset.css' denied.

settings.py

AWS_ACCESS_KEY_ID=os.environ.get('AWS_ACCESS_KEY_ID',None)
AWS_SECRET_KEY=os.environ.get('AWS_SECRET_KEY',None)
AWS_SECRET_ACCESS_KEY=os.environ.get('AWS_SECRET_KEY', None)
AWS_STORAGE_BUCKET_NAME = 'intro-story'
AWS_S3_HOST='s3.us-east-2.amazonaws.com'
AWS_S3_CUSTOM_DOMAIN = 's3.us-east-2.amazonaws.com/%s' % AWS_STORAGE_BUCKET_NAME

AWS_S3_SECURE_URLS = False

STATICFILES_LOCATION = 'static'
STATICFILES_STORAGE = 'custom_storages.StaticStorage'
STATIC_URL = "https://%s/%s/" % (AWS_S3_CUSTOM_DOMAIN, STATICFILES_LOCATION)

MEDIAFILES_LOCATION = 'media'
MEDIA_URL = "https://%s/%s/" % (AWS_S3_CUSTOM_DOMAIN, MEDIAFILES_LOCATION)
DEFAULT_FILE_STORAGE = 'custom_storages.MediaStorage'


try:
    from .local_settings import *
except ImportError:
    pass

local_settings.py

AWS_ACCESS_KEY_ID = "xxx"
AWS_SECRET_ACCESS_KEY = "yyy"

custom_storages.py

from django.conf import settings
from storages.backends.s3boto import S3BotoStorage

class StaticStorage(S3BotoStorage):
    location = settings.STATICFILES_LOCATION

class MediaStorage(S3BotoStorage):
    location = settings.MEDIAFILES_LOCATION

EDIT:


I managed to get it working by messing around with various values in the settings.py, but is still not right.

Here is all the stuff relating to static and media paths

STATICFILES_LOCATION = 'static'
MEDIAFILES_LOCATION = 'media'

import custom_storages

STATIC_URL = "https://%s/%s/" % (AWS_S3_CUSTOM_DOMAIN, STATICFILES_LOCATION)
STATICFILES_STORAGE = 'custom_storages.StaticStorage'

MEDIA_URL = "https://%s/%s/" % (AWS_S3_CUSTOM_DOMAIN, MEDIAFILES_LOCATION)
DEFAULT_FILE_STORAGE = 'custom_storages.MediaStorage'

Here is custom_storage that I'm importing:

from django.conf import settings
from storages.backends.s3boto import S3BotoStorage

class StaticStorage(S3BotoStorage):
    location = settings.STATICFILES_LOCATION

class MediaStorage(S3BotoStorage):
    location = settings.MEDIAFILES_LOCATION

Now, if i comment out

STATICFILES_STORAGE = 'custom_storages.StaticStorage'

It will load the static files from S3 and all is fine, BUT collectstatic fails. If I uncomment that line, collectstatic works but it gives an error when I try and load the site. The error is:

 # Ensure final_path starts with base_path and that the next character after
    # the final path is '/' (or nothing, in which case final_path must be
    # equal to base_path).
    base_path_len = len(base_path)
    if (not final_path.startswith(base_path) or
            final_path[base_path_len:base_path_len + 1] not in ('', '/')):
        raise ValueError('the joined path is located outside of the base path' ...
                         ' component')
    return final_path.lstrip('/')

So clearly something is up with that custom_storage part but I have no idea what :/

1
If you right-click and inspect the href for reset.css what url you get? If you open a different browser and paste that url, are you able to see it?nik_m
Also, if inside the S3 panel, you click on permissions of that file what do you get? Do you have under the Group the value of everyone and under the Object access the value of Read?nik_m
I cant actually see the href link - when i try and view the site I either get the 400 error (Heroku) or the Django debug screen from where most of the above info was lifted. The permissions under "Everyone" is set to read, so is "Any authenticated AWS User" and then the one user i created through IAM is set to read/write. Also maybe worth noting that if i grab the full URL to this reset.css file from the S3 Admin and hardcode the path (instead of using the {% static %} tag), it works and just finds a problem with the next stylesheet in the HTML.Zeb
Does the href link that you see matches the one from the S3 Admin?nik_m
I dont see a href link other than whats in the original info above, i.e. Attempted access to '/css/reset.css' denied. as the main exception, and: Exception Value: Attempted access to '/css/reset.css' denied at the end of the traceback. So it doesnt reference the Amazon/S3 path anywhere in the errors - but not sure if it would if there was a problem with the way STATIC was set up/configured as isn't that the part that builds the main part of the URL?Zeb

1 Answers

23
votes

Sorted! As expected it was something annoyingly simple but perhaps this will help anyone else who hits the same wall.

This:

<link rel="stylesheet" type="text/css" href="{% static '/css/reset.css' %}">

Needed to be this:

<link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}">

I.e. no leading slash in the href. Worked fine using local static files storage but broke the S3 link.