Using sass with Flask and jinja2 - python

I would like to include a sass compiler in my Flask application. Is there a generally accepted way of doing this?

Flask-Assets extension (which uses webassets library) can be used for that. Here's how to configure it to use pyScss compiler (implemented in Python) for SCSS:
from flask import Flask, render_template
from flask.ext.assets import Environment, Bundle
app = Flask(__name__)
assets = Environment(app)
assets.url = app.static_url_path
scss = Bundle('foo.scss', 'bar.scss', filters='pyscss', output='all.css')
assets.register('scss_all', scss)
And in the template include this:
{% assets "scss_all" %}
<link rel=stylesheet type=text/css href="{{ ASSET_URL }}">
{% endassets %}
SCSS files will be compiled in debug mode as well.
pyScss only supports SCSS syntax, but there are other filters (sass, scss and compass) which use the original Ruby implementation.

Some things have changed since the question was answered in 2013.
You can't have scss installed at the same time as pyscss and expect the pyscss filter to work like in the accepted answer.
scss = Bundle('foo.scss', 'bar.scss', filters='pyscss', output='all.css')
I was getting an error that ended in:
File "/home/sri/crap/example/flask/lib/python2.7/site-packages/webassets/filter/pyscss.py", line 110, in setup
scss.config.STATIC_ROOT = self.static_root or self.ctx.directory
You have to remove scss (i.e. pip uninstall scss) and be sure that pyscss is installed (i.e. pip install pyscss).
Also note that you will have to set some environment variables in order to get pyscss to work as well:
app = Flask(__name__)
assets = Environment(app)
assets.url = app.static_url_path
scss = Bundle('index.scss', filters='pyscss', output='all.css')
assets.config['SECRET_KEY'] = 'secret!'
assets.config['PYSCSS_LOAD_PATHS'] = assets.load_path
assets.config['PYSCSS_STATIC_URL'] = assets.url
assets.config['PYSCSS_STATIC_ROOT'] = assets.directory
assets.config['PYSCSS_ASSETS_URL'] = assets.url
assets.config['PYSCSS_ASSETS_ROOT'] = assets.directory
assets.register('scss_all', scss)
see the documentation on the pyscss filter for more info: http://webassets.readthedocs.io/en/latest/builtin_filters.html#pyscss
I hope that this save someone else a lot of time because I wasted a whole day on it.

I think the most pythonic approach is this one line solution using libsass. After you import sass simply use the compile method with the dirname keyword argument, like this:
sass.compile(dirname=('path/to/sass', 'path/to/css'))
You also have the option to set the output style, for example:
sass.compile(dirname=('path/to/sass', 'path/to/css'), output_style='compressed')
If you want to watch a file or directory for automatic compilation on every edit use boussole.

Currently, exist a better approach for this issue, the extion Flask-Scss.
You just have to install it: pip install Flask-Scss
And instanciate a Scss object after configuring the application (probably in your manage.py file):
from flask import Flask
from flask.ext.scss import Scss
app = Flask(__name__)
Scss(app)
By default, the extension will look for your .scss files in {app.root_dir}/assets/scss or {app.root_dir}/assets and will put the generate .css files in {default_static_dir}/css or {default_static_dir}.

Libsass is a good solution for this.
# Your __init__.py file
from flask import Flask
from sassutils.wsgi import SassMiddleware
app = Flask(__name__)
app.wsgi_app = SassMiddleware(app.wsgi_app, {
'myapp': ('static/sass', 'static/css', '/static/css')
})
# Your HTML (Jinja) file
<link href="{{ url_for('static', filename='css/style.scss.css') }}"
rel="stylesheet" type="text/css">
More info:
https://sass.github.io/libsass-python/index.html

Related

Flask host_matching and static files when using multiple top-level domains

So I have two domains:
MAIN_DOMAIN = www.example.com
DUTCH_DOMAIN = www.voorbeeld.com
I want to use the dutch domain with Flask-Babel for i18n support.
To make this possible I'm using host_matching like this:
def create_app(config_class=Config):
app = Flask(__name__, host_matching=True,
static_host=MAIN_DOMAIN)
Finally, routing:
#bp.route('/', host=MAIN_DOMAIN)
#bp.route('/', host=DUTCH_DOMAIN)
def index():
return render_template('index.html')
I might want to change the routing part in the future. But for now this works fine.
Now for the problem, when I navigate to the dutch domain everything works, except that I get many 404 error messages because it can't find the static files. If I change the url of the static files to the MAIN_DOMAIN it works fine. Which makes sense since static_host is set to MAIN_DOMAIN.
So I just can't seem to find a solution for this problem. Is there a way to make both domains for static files work? Or how would you solve this?
Ok. So I've figured it out and actually noticed some interesting behaviour.
So when I go to the main domain. url_for works as expected, so if you would do something like this in your templates:
<script src="{{ url_for('static', filename='jquery/dist/jquery.min.js') }}"></script>
The result would be this:
<script src="/static/jquery/dist/jquery.min.js"></script>
However, when I go to the DUTCH_DOMAIN route I get:
<script src="http://www.example.com/static/jquery/dist/jquery.min.js"></script>
So you don't have to manually add _external=True to url_for for you other domains but it all happens automagically. The reason it didn't work before is because of CORS since from the browser point of view you're loading assets from a different domain instead of from the "local" website.
Hope this helps.

How to use a Jinja2 extension direct from a Flask render_template

I'm working on a Flask app that is working just fine, until I try to add the following line to a template to be rendered:
{% do totals.update({tier: 0}) %}
The current code for rendering the template uses Flask's render_template():
from flask import Flask, Response, request, session
from flask import render_template
app = Flask(__name__)
..
return render_template(<template.htlm>,...)
This fails with with following error:
TemplateSyntaxError: Encountered unknown tag 'do'. Jinja was looking for the following tags: 'endfor' or 'else'. The innermost block that needs to be closed is 'for'.
The obvious fix is to add the jinja2.ext.do extension to jinja. I have been able to do so successfully using Jinja2 directly, as per:
from jinja2 import Environment, PackageLoader
ENV = Environment(loader=PackageLoader('ticket_app', 'templates'), extensions=['jinja2.ext.do'])
...
TEMP = ENV.get_template('div_' + div_id + '.html')
return TEMP.render(sales_new=sales_new, event_config=event_config)
However, I would prefer to not use Jinja2 directly... The app was only using Flask and render_template() before, and as render_template() uses Jinja2 under the hood (as far as I understand) it seems that it should be possible to make render_template() understand the jinja2.ext.do extension (or any other extension for that matter).
So far, I've tried the following:
app = Flask(__name__)
app.config['TEMPLATES_AUTO_RELOAD'] = True
app.config['EXPLAIN_TEMPLATE_LOADING'] = True
env = app.jinja_env
env.add_extension('jinja2.ext.do')
While the above does not throw an error, it is also not causing it to make render_template() understand the jinja2.ext.do extension.
Any suggestions? Should this be possible? If so, how?
Update for Flask v.2.0 :
Since v.2.0, the below solution raise a KeyError: 'extensions' exception. Try this instead.
For Flask v.1.1, you can directly access to the Jinja extensions loaded by Flask with the Flask.jinja_options dictionary. In your case, adding just this line should do the trick:
app = Flask(__name__)
app.jinja_options['extensions'].append('jinja2.ext.do')
Make sure you update your Flask (using pip: pip install -U Flask).
I actually found that the option that Zatiranyk outlined broke sometimes (specifically when I tried to use Flask-SocketIO.
app.jinja_options['extensions'].append('jinja2.ext.do')
The solution that I found was to use the below option instead:
app.jinja_env.add_extension('jinja2.ext.do')

How to load jinja template directly from filesystem

The jinja API document at pocoo.org states:
The simplest way to configure Jinja2 to load templates for your application looks roughly like this:
from jinja2 import Environment, PackageLoader
env = Environment(loader=PackageLoader('yourapplication', templates'))
This will create a template environment with the default settings and a loader that looks up the templates in the templates folder inside the yourapplication python package.
As it turns out, this isn't so simple because you have to make/install a python package with your templates in it, which introduces a lot of needless complexity, especially if you have no intention of distributing your code.
I found these related questions about doing so, but the answers are vague and unsatisfying:
need to package jinja2 template for python
How to make a python package containing only jinja templates
How can I load the template directly from the filesystem, not as a resource in a package?
Use a FileSystemLoader instead of a PackageLoader. Suppose there is a python file in the same directory as the template:
./index.py
./template.html
This index.py will find the template and render it:
#!/usr/bin/python
import jinja2
templateLoader = jinja2.FileSystemLoader(searchpath="./")
templateEnv = jinja2.Environment(loader=templateLoader)
TEMPLATE_FILE = "template.html"
template = templateEnv.get_template(TEMPLATE_FILE)
outputText = template.render() # this is where to put args to the template renderer
print(outputText)
In the introduction, the PackageLoader approach seems to be presented as the default, "simplest" method; however, there is also a section which discusses all the built-in loaders.
A simpler way is to directly call the jinja2.Template constructor and use open to load the file:
from jinja2 import Template
with open('template.html.jinja2') as file_:
template = Template(file_.read())
template.render(name='John')
Here is the one liner:
from jinja2 import Template
with open('template_file.j2') as f:
template = Template(f.read())
Then you can render the template on another line, or for all in one line:
with open('template_file.j2') as f:
rendered = Template(f.read()).render(var="TEXT")
If using Python 3.4+ and Jinja2 - v2.11+ -- we can combine python's pathlib and Filesystem to simplify the flow
from pathlib import Path
...
p = Path(__file__).parent.parent / 'templates' # sample relative path
env = Environment(
loader=FileSystemLoader(Path(p)))
template = env.get_template('your_file.jinja2')
I am not comfortable with using directly Template(file) since Jinja's template inheritance processing may not work well.
Pathlib support is only added in latest version of Jinja - v2.11+
from jinja2 import Environment, select_autoescape, FileSystemLoader
env = Environment(loader=FileSystemLoader(
searchpath=folder_contain_list_html), autoescape=select_autoescape(['html', 'xml']))
template = env.get_template('file_name_detail_template')
body_html = template.render(**args)
send_email(body_html)

How to load static files in python bottle in URL's

I'm working on a python app in Python Bottle. The app works fine if I'm on 1 lvl deep URLs like /dashboard or /rules or /page. However, if I go deeper like /dashboard/overview or /rules/ruleone or /page/test the CSS, JS, fonts and images will fail. :(
The HTML source code still poinsts to /assets/ but if I'm on an URL like /rules/ruleone, the right path should be something like ../assets or ./assets right? The path /assets/ only works on the first level but not on deeper lvls, in other words: bottle doesnt adapt the static file path to the current directory. How do I fix this?
I'm stuck on this problem for days now, I realy hope someone can help me. :(
My code (simplified):
#!/usr/bin/env python
import lib.bottle as bottle
from lib.bottle import route, template, debug, static_file, TEMPLATE_PATH, error, auth_basic, get, post, request, response, run, view, redirect, SimpleTemplate, HTTPError, abort
import os, sys, re
#route('/dashboard')
#view('secure_page')
def show__page_dashboard():
return dict(page = 'Dashboard')
#route('/rules/<rule>')
#view('secure_page')
def show_page_rules_more(rule):
return dict(page = rule)
#route('/assets/<filepath:path>')
def server_static(filepath):
return static_file(filepath, root='/var/myapp/assets')
TEMPLATE_PATH.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "view")))
bottle.debug(True)
from lib.bottledaemon import daemon_run
if __name__ == "__main__":
daemon_run()
So my app runs in daemon mode. The structure is:
lib
bottle.py
bottledaemon.py
assets
css
js
...
view
secure_page.tpl
footer.tpl
header.tpl
...
server.py
I hope someone can help me out on this, thanks in advance guys, I love u! <3
Alright I found the solution for my problem. Bottle offers an URL tag to dynamicly build URLs.
from bottle import url
#route('/dashboard')
#view('secure_page')
def show__page_dashboard():
return dict(page='Dashboard', url=url)
#route('/assets/<filepath:path>', name='assets')
def server_static(filepath):
return static_file(filepath, root='/var/myapp/assets')
This is how I load my CSS/JS/images
<link href="{{ url('assets', filepath='css/style.css') }}" rel="stylesheet" type="text/css"/>
Dynamic menu URL's (in the navigation for example) is done this way:
{{ url('/dashboard') }}
I hope this info will help someone who is strugling with the same problem as I was.
Tested on v0.12 and v0.13dev
I do use:
#route('/css/<filename>')
def stylesheets(filename):
return static_file(filename, root='./static/css/')
and in template use:
<link href="/css/style.css" rel="stylesheet">
can be replicated for all assets items (img, css, and js)

initializr with bottlepy - css not loaded

I'm trying to use a boilerplate I downloaded from Initializr together with bottle.py. I'm obviously doing something wrong since when I simply try to load index.html the site renders without using any of the stylesheets and I get the following errors in the browser console:
Use of getUserData() or setUserData() is deprecated. Use WeakMap or element.dataset instead. requestNotifier.js:52
The stylesheet http://localhost:8080/css/normalize.min.css was not loaded because its MIME type, "text/html", is not "text/css". localhost:8080
The stylesheet http://localhost:8080/css/main.css was not loaded because its MIME type, "text/html", is not "text/css". localhost:8080
SyntaxError: syntax error modernizr-2.6.2-respond-1.1.0.min.js:1
SyntaxError: syntax error plugins.js:1
SyntaxError: syntax error
My app looks like this:
import bottle # Web server
from bottle import run, route, static_file, error, template # import request
#route('/')
def index():
return static_file('index.html', root='./html/')
#route('./css/<filename>')
def server__static(filename):
return static_file(filename, root='./css/')
if __name__ == '__main__':
# To run the server, type-in $ python server.py
bottle.debug(True) # display traceback
run(host='localhost', port=8080, reloader=True)
The stylesheets are called from index.html like so:
<link type="text/css" rel="stylesheet" href="css/normalize.min.css">
<link type="text/css" rel="stylesheet" href="css/main.css">
My basic folder structure is (snipped js folder etc.):
bottle_test.py
- html
index.html
- css
main.css
normalize.min.css
I understand that bottle doesn't serve static files by itself and I did play around with the route to server-static and added, changed and removed the mimetype argument but can't get it to work.
The entire boilerplate sits in the html folder and the app does find index.html. I tried this in Firefox and Chrome. I'm on Win 8.1 and anaconda's python 2.7 if that matters. What am I doing wrong?
Your paths seem wrong. The CSS route shouldn't start with a ., seems it interferes with how Bottle parses it. And your CSS static_file root doesn't properly reflect the css folder placement relative to the working directory.
This should work alright:
#route('/css/<filename>')
def server__static(filename):
return static_file(filename, root='./html/css')

Categories

Resources