Using LESS with Django on Heroku

I’m currently working on a Django project that will be deployed on Heroku, and I’m building the front end of this project with Bootstrap. I want to be able to use LESS for this project, so I needed to figure out a way to compile the .less files into css. The easiest way to do this is to have your LESS files compiled into CSS on the client side, using less.js. I started off including all of the LESS that comes with Bootstrap, and I found that compiling all of it to CSS on the client side really slowed down page loads. For performance, the best practice is to pre-compile your LESS on the server to CSS and just serve the CSS file. There’s no reason to make clients download all of your less, plus less.js, then do the work to compile your CSS files before they can render your pages.

This post assumes that you already have your Django project set up in a virtualenv on your local machine, and that you are already set up to deploy to Heroku.

Local Development Environment

First, we’ll address using LESS in your local development environment. For this, we are going to use django_compressor to compress our CSS. This package can be used to compress multiple css files into a single file, and in the process of doing this, you can run pre-compilers on your CSS, like LESS, and it will run this using the command lessc. Before being able to do this, we need to install LESS using npm, the node package manager. In order to run this, we need node.js, and we can get this using nodeenv, a virtual environment for node. You can add nodeenv to your python virtual environment as follows. First, make sure your python virtualenv is active.

pip install nodeenv
nodeenv --python-virtualenv #this command took a long time, be patient

Now you can install LESS with node package manager and install Django Compressor:

npm install -g less
pip install django_compressor

Set up compressor according to the django_compressor documentation. In my case, this involved updating settings.py to include ‘compressor’ under INSTALLED_APPS, and adding the following:

COMPRESS_ENABLED=True
if not os.environ.has_key('COMPRESS_OFFLINE'):
    COMPRESS_OFFLINE=True #this is so that compress_offline is set to true during deployment to Heroku
COMPRESS_PRECOMPILERS = (
    ('text/less','lessc {infile} {outfile}'),
)

In your local .env file, add:

COMPRESS_OFFLINE=False

I also had to add {% load compress %} to my base template, and wrapped my stylesheet link tag in the compress template tag:

{% compress css %}
<link rel="stylesheet" type="text/less" href="{% static 'less/main.less' %}">
{% endcompress %}

It’s important to have the type=”text/less” in the tag, or else Django Compressor won’t know that it needs to run the precompiler on it. Django Compressor is going to be compressing the LESS file that’s in your /static directory, or the file that’s copied by collectstatic. That means that every time you want to view the result of updates made to your LESS file, you will have to run collectstatic. I used this solution by Katy Lavallee to use a python package called watchdog to run collectstatic automatically every time I updated my LESS file. Here are the steps I had to follow to get this working:

brew install libyaml #install libyaml with homebrew
pip install argcomplete
pip install watchdog

Now, while you are developing, you will need an additional terminal window with the following command running in it:

watchmedo shell-command -R -c "python manage.py collectstatic --noinput;python manage.py compress --force" app/static/app/less

Where ‘static’ is the path to the directory in your application that you want to watch for changes, relative to the current directory that the command is being run from. I made an alias for this command in my local .bash_profile:

alias watch="watchmedo shell-command -R -c "python manage.py collectstatic --noinput;python manage.py compress --force" app/static/app/less"

Now when I need to work on LESS files for this project, I need to open two terminal windows, activate virtual environment in each of them, activate the watch command in one of them, and start up a server in the other one. When I update any files in the watched directory, watchdog will run the collectstatic command, and Django Compressor will compress the copied files into the CSS file that the browser loads.

Setting up LESS on Heroku

Now we need to get LESS compiling on Heroku. Our app is going to need access to node.js and LESS on the server. By default, our Django application is going to be built using the python buildpack on Heroku, which can install everything in our requirements.txt, but won’t have access to node.js, and won’t be able to install our LESS compiler with npm. What we need to do is to install node.js and LESS on Heroku after our application is compiled. To get this working, I followed this post by Filip Wasilewski. All I had to do was create a folder named ‘bin’ in my Django project, copy the scripts install_less, install_nodejs, post_compile, and run_compress into it, and add COMPRESS_OFFLINE = True to my settings.py file. Now when I deployed to Heroku, node.js and LESS were installed, and I had Django Compressor generating my CSS.

References

The following links were helpful in this effort:

UPDATE:

I had to make a couple of modifications to the above in order to get this working with Heroku’s config vars. To deploy with django compressor on Heroku, COMPRESS_OFFLINE has to be True during build, but I needed it to be false on my local machine. To get this to work, you need to have COMPRESS_OFFLINE=False in your local .env file, and set it to True in your settings.py file if it is not available in the environment, which it won’t be during build on Heroku.

Google App Engine Local Setup

My goal was to configure my local machine so I could write Django web apps, and have them use a local MySQL database, and run under the Google App Engine development environment. This was a first step for me towards deploying Django apps on Google App Engine. As a later step, I may try to learn about how to use the App Engine Datastore, a nonrelational database storage solution with Django.

Installing MySQL with Homebrew

I started by following the Google documentation on Building an application with a local MySQL instance. It recommended installing MySQL through MacPorts. I already had Python2.7 installed, and was used to using local MySQL through MAMP. I ran into a great deal of trouble with this, I kept getting checksum errors with the MySQL port, and with getting it to work with mysql-python. I decided to see if I could get it to work with Homebrew instead, and I uninstalled MacPorts.

To install homebrew, open up a terminal and paste:

ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"

Then, you can install and start mysql:

brew install mysql
mysql_install_db
mysql.server start

Change the root mysql password:

mysql -u root -p

You will be prompted to enter the new root password. I use Sequel Pro to manage databases. Using the password you just set up, you can connect to your local database through Sequel Pro by using host: 127.0.0.1, user: root, and the password you entered above. Then, you can create a new database, add a user, and give that user privileges to access the database. You will need this later.

You can also install python 2.7 with Homebrew if you don’t already have it installed:

brew install python

Installing Virtualenv

I’ve only worked on one python project before this, and didn’t use virtualenv. That project was a learning experience for me, and I struggled with getting the local python setup, packages, paths, and environment variables straight. I knew there might be some problems in my current default environment because of this (that may have caused some problems with MacPorts earlier), so from here forward, it seemed like it would make the most sense to use virtualenv. virtualenv is a straightforward way to set up separate python environments on your system with individual packages and configurations. I followed this tutorial to set up virtualenv. Here is what I did:

cd ~/code/
mkdir virt_env
cd virt_env
virtualenv virt1

Activate your virtual environment

source virt1/bin/activate

I created an alias for activation of this virtual environment

sudo nano ~/.bash_profile

Added this line:

alias ve1="source ~/code/virt_env/virt1/bin/activate"

Reload bash profile:

source ~/.bash_profile

Now you can load your virtual environment with:

ve1

and you should see (virt1) at the beginning of your terminal prompt, meaning you are in the virtual environment. To exit, type:

deactivate

While in the virtual environment, you can install packages:

pip install django
pip install mysql-python

Django Setup

Choose a location where you want your django projects to live, and go to that directory in the terminal. Create a new django project with this command, replacing ‘mysite’ with the name of your project:

django-admin.py startproject mysite

This will create a directory called mysite, with manage.py and a mysite application folder inside of it. In the top mysite directory, create a file called app.yaml. This is the google app engine config file:

application: appproject
version: 1
runtime: python27
api_version: 1
threadsafe: true

libraries:
- name: django
  version: "1.5"

builtins:
- django_wsgi: on

env_variables:
  DJANGO_SETTINGS_MODULE: 'mysite.settings'

handlers:
- url: /static/admin
  static_dir: static/admin
  expiration: '0'

For local development, the application name can be anything, but when you deploy to google app engine, this has to match a registered application id. Make sure that you replace “mysite” under env_variables with the name of your django project.

Make sure you have downloaded and installed the Google App Engine SDK for Python. Once this is done, you should be able to run your django app using:

dev_appserver.py mysite

When you go to http://127.0.0.1:8080, you should see the default Django site. In the terminal, press Cmd+C to quit the server. Edit settings.py under mysite/mysite to include your database connection information:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': 'mysite',                      # Or path to database file if using sqlite3.
        # The following settings are not used with sqlite3:
        'USER': 'myuser',
        'PASSWORD': 'mypassword',
        'HOST': '',                      # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
        'PORT': '',                      # Set to empty string for default.
    }
}

Now, run syncdb:

python manage.py syncdb

If this is successful, you should be able to refresh your database in Sequel Pro and see that the Django tables have shown up in your database. Run your django app again:

dev_appserver.py mysite

You now have a working django app with local MySQL working on the development Google App Engine launcher.