Flask static_folder hosted on S3 - python

I am trying to reroute all of my /static content to host on Amazon S3. My first thought was to use global config['path'] throughout my jinja templates, but this won't work for external css and js files, plus it is kind of messy. I found the static_folder and static_url_path released in 0.7 and this seems like what I want. However, when I go to http://localhost:8000/static/img/abc.jpg it does not locate the files on S3. Am I using this feature right or is there some other way to do this?
Thanks!

I recently developed a Flask extension to deal with just this situation. It's called Flask-S3, and you can read the documentation for it here.
As an example, here is how you would integrate it into your Flask application:
from flask import Flask
from flask_s3 import FlaskS3
app = Flask(__name__)
app.config['S3_BUCKET_NAME'] = 'mybucketname'
s3 = FlaskS3(app)
You can use the extension to upload your assets to your chosen bucket:
>>> from my_application import app
>>> from flask_s3 import create_all
>>> create_all(app)
Flask-S3 is blueprint aware, and will upload all assets associated with any registered blueprints.

I wish the flask.Flask constructor's static_url_path argument would accept a full URI, but it screams about the path needing a leading slash when you try that. If it accepted it, you could just use url_for after setting static_url_path='http://my_s3_bucket.aws.amazon.com/' or so.
Another possible solution is to use a context processor. I believe this is the cleanest solution, but I don't have much experience with flask. It sure looks clean. Using a context processor, I pull the URL from the environment (I'm using heroku so it's wicked easy to set). Then the context processor makes the static_url variable available in my templates.
In app.py:
# Environment has STATIC_URL='http://my_s3_bucket.aws.amazon.com/'
#app.context_processor
def inject_static_url():
"""
Inject the variable 'static_url' into the templates. Grab it from
the environment variable STATIC_URL, or use the default.
Template variable will always have a trailing slash.
"""
static_url = os.environ.get('STATIC_URL', app.static_url_path)
if not static_url.endswith('/'):
static_url += '/'
return dict(
static_url=static_url
)
In the template:
<link rel="stylesheet" type="text/css" href="{{ static_url }}css/main.css" />
The result
<link rel="stylesheet" type="text/css" href="http://my_s3_bucket.aws.amazon.com/css/main.css" />

I know this is an old question but here's how I get around this issue.
Import Flask's url_for method under a different name (e.g., flask_url_for)
Create your own custom url_for method, which calls flask_url_for and then uses string replacement to add in your S3 bucket url.
I like this strategy because it's simple and intuitive to use, and the interface is exactly like the original url_for method.
from flask import url_for as flask_url_for
from flask import current_app
PRODUCTION_URL = 'https://s3.amazonaws.com/MY-BUCKET-NAME'
def url_for(path, **kwargs):
url = flask_url_for(path, **kwargs)
if url.startswith("/static") and not current_app.debug:
url = url.replace("/static", "", 1)
return PRODUCTION_URL + url
return url

what you are trying to accomplish is to be able to write
{{ url_for(app.static, file='img/abc.jpg', _external=True) }}
and have that resolve to a url in s3?
i believe that static_folder and static_url_path are setup so that if you call
app = Application('app', static_folder='foo', static_url_path='bar')
then going to
the above url_for would output
http://localhost:8000/bar/img/abc.jpg and it would look for static/img/abc.jpg in the filesystem.
i think what you want to do is potentially write a custom filter that would translate your path to an s3 url so that you could make a call something like
{{ 'img/abc.jpg'|s3_static_url }}
there's some documentation on that here: flask custom filters

What you need is the Amazon S3 public file URL instead of http://localhost:8000/static/img/abc.jpg. If its just a few files, maybe hardcode them in your template with the full URL or if you are going to have a local version for testing and use S3 on production site (like we do) then you can try the following method.
Define a new variable probably called STATIC_ROOT in the top of your base template (which other templates inherit), and use that as the file path prefix.
For example:
{% if app.debug %}
{% set STATIC_ROOT = url_for('static', filename='') %}
{% else %}
{% set STATIC_ROOT = 'http://s3.amazonaws.com/bucketname/' }
{% endif %}
This will give you the default path when you might be testing your application locally, and the public S3 url when in production (based on debug flag).
Using it:
<script src="{{ STATIC_ROOT }}js/jquery.min.js">

Related

Build HTML from Django View

I need to construct the HTML body from a Django view and I cannot find a solution to refer correctly a JPG file ( must say that the HTML is much larger then this, but with other stuff seems that is working for me
):
I've tried this:
from django.template import Template
...
html = Template('<IMG SRC="{% static "base/images/course/website-46-2.jpg" %}">')
return HttpResponse( html )
And I get this error:
Invalid block tag on line 1: 'static'. Did you forget to register or load this tag?
In Django template I resolve this by loading the static files:
{% load static %}
How can I do this in Python ( Django View ) ? Or any other suggestion is much appreciated.
I've tried different solution that I have found on this site and others but none seems to work for me.
Django Version: 2.2.1
You can create an engine with the static library as a built-in. This makes it available to the template without calling {% load static %} first.
from django.template import Template, Context, Engine
engine = Engine(builtins=['django.templatetags.static'])
template = engine.from_string('<IMG SRC="{% static "base/images/course/website-46-2.jpg" %}">')
return HttpResponse(template.render(Context()))
Have you set your STATIC_URL in settings.py? You can do this by the following:
STATIC_URL = '/static/'
Your image would then be found under 'your_app/static/base/images/course/website-46-2.jpg'.
Does the folder structure follow this convention? If not you can set the STATIC_URL to '/base/'
def startchat(request):
template=loader.get_template('app1/chatbot.html')
return HttpResponse(template.render())
This function loads the html page into Django.
Before that , we need to import the module
from django.template import loader

Serve static files from a CDN rather than Flask in production

In my Flask app I serve the static assets through the app in the dev env, but I'd like to use a CDN in production. Every asset is loaded in a template called base.html, so I guess the easiest solution is to pass a variable to the render function and use it in the template like:
<script src="{{ STATIC_URL }}/js/main.js"></script>
Normally it would be an empty string in the dev env, and the cdn url in production. I'd like to avoid passing this STATIC_URL variable to every view. I could make it work with
#bp.context_processor
def set_static_path():
return dict(STATIC_URL='https://foo.bar.com')
But for me this seems a little hacky. Is there a better way to solve this problem?
There's no need to change how you link to static files, you can still use url_for('static', filename='myfile.txt'). Replace the default static view with one that redirects to the CDN if it is configured.
from urllib.parse import urljoin
# or for python 2: from urlparse import urljoin
from flask import redirect
#app.endpoint('static')
def static(filename):
static_url = app.config.get('STATIC_URL')
if static_url:
return redirect(urljoin(static_url, filename))
return app.send_static_file(filename)
Whether you're on a dev machine or production, set the STATIC_URL config value to the CDN and requests for static files will be redirected there.
Redirects are relatively cheap, and are remembered by browsers. If you get to the point where performance is meaningfully affected by them, you can write a function that links directly when using the CDN.
#app.template_global()
def static_url(filename):
static_url = app.config.get('STATIC_URL')
if static_url:
return urljoin(static_url, filename)
return url_for('static', filename=filename)
The template_global decorator makes the function available in all templates. Use it instead of url_for when you need urls for static files.
There may be a Flask extension that does this for you already. For example, Flask-S3 provides a url_for that serves static files from AWS S3.
This flask cdn integration guide may be worth taking a read through. Basically you can install the Flask-CDN extension and define your CDN URL within your app.py file like so:
from flask import Flask
from flask.ext.cdn import CDN
app = Flask(__name__)
app.config['CDN_DOMAIN'] = 'cdn.yourdomain.com'
CDN(app)
I had a similar problem using Flask Assets and Flask-CDN. I found that Flask-CDN was trying and failing to generate timestamps for each asset. Upon failure it would revert to relative URLs.
Adding app.config['CDN_TIMESTAMP'] = False fixed the issue.

Django and S3 - static URL won't change

I managed to set my S3 as a subdomain static.domain.com (using CNAME and bucket name the same as subdomain).Now I'd like to amend my django settings to use this URL.
I use django-storages and can't make it work.
I set STATIC_URL to http://static.domain.com.
When I use {{ STATIC_URL }} in my templates - it works. But when I use {% static %} templatetag, or check my static files in the admin panel I see they use: http://static.comain.com.s3.amazonaws.com
I have no idea how to set it properly to make it work. I'd like to make my static (admin + www) to point to the correct URL which is static.domain.com
It's the same situation with media files.
Any clues? Am I missing some settings?
I found the solution. All I had to do was to set this in my settings:
import boto.s3.connection
AWS_S3_CALLING_FORMAT = boto.s3.connection.VHostCallingFormat()

Django Project with CSS embedded images

I have created a Django project that will be built into various environments: Dev, Test, Prod, etc. I have quite a few CSS files, and some of them have embedded images. For example:
.logo {
background-image: url(http://{server:port}/static/logo.png);
height: 58px;
width: 239px;
}
For each environment, there may be a different server and port that will serve the static content.
What would be the most efficient method (along withe Django's best practices) to allow CSS files to take into account various environments?
Hassan,
The Django best practice for this would be to pass the server port to the image as "context" and have the image called based on that.
So a simple views.py
def image_view(request, server_port):
return render(request, 'image_page.html', {'server_port': server_port})
urls.py
urlpatterns = patterns('',
url(r'image_view/(?P<server_port>\d+)/$', views.image_view, name='image_view'),
)
Then in your image_page.html
<body>
<img src="{{ STATIC_URL }}{{ server_port }}logo.png" alt="" />
</body>
The url to get to this view/html page:
Link
Your are not able to pass context variables to .css files using Django. Not that I am aware of. Hope that helps. Remember to configure STATIC_URL in your settings.py for this to work. And your server_port will also need to match the directories where images are stored in order for Django to look up the image using the file path.
At the end of the day, I decided to just use the "Production" server/port in all my CSS files. Then at each tier prior to Production, just update the host file to point the Prod server/port for static content to the appropriate IP address.

how to get static files in Flask without url_for('static', file_name='xxx')

I don't want use url_for('static', file_name='foo.jpg') to get static file in template.
how to get static file in this way:
<img src="/pic/foo.jpg" />
thanks
You can set up your own route to serve static files. Add this method and update the static path directory in the send_from_directory method, then your img tag should work.
#app.route('/pic/<path:filename>')
def send_pic(filename):
return send_from_directory('/path/to/static/files', filename)
For a production app, you should set up your server to serve static files directly. It would be much faster and use less server resources, but for a few users the difference shouldn't be a problem.

Categories

Resources