I'm setting up a Django development environment using Vagrant to run an Ubuntu virtual machine on VirtualBox. As this is a student project run mostly by very amateur coders and I want everyone to be able to contribute, I want the environment to be as convenient as possible.
What I'm trying to achieve:
- LESS-files would be compiled to CSS automatically whenever they are changed
- This automation would be run immediately on vagrant up, without any need to SSH the virtual machine.
I'm using django-pipeline (as it is supports Python3, unlike some other similar plugins) to manage my static files and compile LESS. Getting the files to compile automatically has proven a bit problematic though, since changing files in the shared folder from the host machine does not trigger inotify events on the VirtualBox guest machine. This rules out the most commonly used options for watching a folder for changes.
One option that I considered was using the Guard-gem with the -p (polling) option, but this would have required setting up Ruby, that I don't use elsewhere in the project, so I abandoned it as too complicated an option.
Inspired somewhat by Django's server's way of monitoring files in the absence of inotify, I decided to write my own script for watching for changes in a folder and then run that script on screen in Vagrant's provisioning script. I got it to work, but since I feel like I have no idea what I'm doing, I want to ask:
- Does this solution make any sense at all or would there have been an insanely easy solution to the original problem that I just missed?
- I'm a newbie to Python. Does my code comply to the best practices of using Django and Python?
- Did I just crack an enigmatic riddle puzzling millions worldwide or should I just be embarrassed about ever spending time on such a petty problem?
The code
myapp/management/commands/watchstatic.py:
from django.core.management.base import NoArgsCommand, CommandError
from django.core.management import call_command
from django.conf import settings
from pipeline.exceptions import CompilerError
import os
import sched
import time
class Command(NoArgsCommand):
help = 'Watches the static folder for changes, and runs the collectstatic-command if something is changed'
def handle_noargs(self, **options):
def get_time(filename):
stat = os.stat(filename)
mtime = stat.st_mtime
return mtime
def files_and_times(directory):
files = []
files_dict = {}
for (dirpath, dirname, filenames) in os.walk(directory):
for name in filenames:
files.append(dirpath + '/' + name)
for filename in files:
files_dict[filename] = get_time(filename)
return files_dict
def read_all():
all_files = {}
for key, sets in settings.WATCH_STATIC_FOLDERS.items():
folder_dict = files_and_times(sets['folder'])
all_files[key] = folder_dict
return all_files
def files_changed(sc, old_files={}):
# Read new files and their modification times
new_files = read_all()
if new_files != old_files:
print('Files changed!')
try:
call_command('collectstatic', interactive=False)
# Delete the useless files created by django-pipeline
# See: https://github.com/cyberdelia/django-pipeline/issues/202
for key, sets in settings.WATCH_STATIC_FOLDERS.items():
for filename in sets['delete']:
filepath = sets['folder'] + '/' + filename
os.remove(filepath)
print('Deleted ' + filepath)
except CompilerError:
print('Failed to compile!')
# Just try again when the code is changed next time
pass
old_files = read_all()
sc.enter(settings.WATCH_INTERVAL, 1, files_changed, (sc, old_files))
s = sched.scheduler(time.time, time.sleep)
# The files are always compiled on the first run
s.enter(settings.WATCH_INTERVAL, 1, files_changed, (s,))
s.run()
settings.py:
WATCH_INTERVAL = 5 # Seconds
WATCH_STATIC_FOLDERS = {
'myapp': {
'folder': 'myapp/static',
'delete': (
'bootstrap_less/bootstrap.css',
'style.css'
)
}
}
In Vagrant provisioning shell script:
su - vagrant -c "cd /vagrant && screen -S watcher -d -m python manage.py watchstatic"
files_changed(sc, old_files={})
you have a dictionary as a default value which might cause issues, see docs.python-guide.org/writing/gotchas/… \$\endgroup\$