def up(self): """ Update a django environment with latest settings. This command does everything required to get a django project running. These are the steps that will be executed when the command is issued: 1. pip install requirements 2. syncdb 3. migrate 4. loaddata initial 5. collectstatic 6. assets build It has been designed with rerunning in mind and can be safely executed without any unwelcome side affects. For example, any pip packages that have already been installed will be skipped on rerun. Any database tables will be preserved (no dropping data), etc. Note that this must be run in a virtualenv. blt will detect if you are not using one and will barf with a helpful error. Usage: blt django.up """ django_root = self.cfg['django']['DJANGO_ROOT'] # install packages from pip's requirements.txt local('pip install -r requirements.txt') with cd(django_root): try: local('python manage.py syncdb') local('python manage.py migrate') except: msg = '\n'.join(["ERROR: Python couldn't find django. Are you in a virtualenv?" , "Try workon MY_SWEET_VIRTENV_HERE"]) abort(msg) with cd(django_root): # Load dev data local('python manage.py loaddata initial') # collect static files local('python manage.py collectstatic --noinput') # Compile static asset bundles local('python manage.py assets build')
def migrate(self, *apps): """ Applies all outstanding deltas to the database. Here the south API is a little friendlier and you can apply migrations to the entire django project. Or, if you so desire, you can apply only to specific apps. Just pass the list as an argument. Args: apps: list of apps to apply migrations, if none given, will run migrations across the entire project Usage: blt south.migrate [apps] Examples: blt south.migrate - default, runs all migrations blt south.migrate invest payment - runs migrations for invest and payment apps """ with cd(self.cfg['django']['DJANGO_ROOT']): if not apps: local('python manage.py migrate') else: for app in apps: local('python manage.py migrate %s' % app)
def load(self, deal_slug, database): """ Loads a deal from a fixture file. This will load the fixture located in the BUNDLE_ROOT directory to the passed in database target. Special care has been taken to allow this command to detect previously loaded data and can be run multiple times without affecting existing database entries. If you need to push a fresh update, use the ``delete`` command to start with a fresh slate. Args: deal_slug: the unique slug for the deal database: the database to load into Usage: blt bundle.load (deal_slug) (database) Examples: blt bundle.load blumhouse-productions PRODUCTION """ environ["DATABASE_URL"] = self.cfg["bundle"][database] fixture_file = self._fixture_file(deal_slug) with cd(self.cfg["django"]["DJANGO_ROOT"]): local("python manage.py bundle load %s" % fixture_file)
def dump(self, deal_slug, database): """ Dumps a given deal out to a fixture file. This makes use of the custom django management command which encapsulates the logic to pull out all of the important model data for a deal. It uses the passed "database" arg to pull the appropriate DATABASE_URL from the bltenv settings. It outputs the fixture to the configured BUNDLE_ROOT. Args: deal_slug: the unique slug for the deal database: the database to dump from Usage: blt bundle.dump (deal_slug) (database) Examples: blt bundle.dump blumhouse-productions STAGING """ environ["DATABASE_URL"] = self.cfg["bundle"][database] fixture_file = self._fixture_file(deal_slug) self._prep_bundle_dir(path.split(fixture_file)[0]) with cd(self.cfg["django"]["DJANGO_ROOT"]): local("python manage.py bundle dump %s %s" % (deal_slug, fixture_file))
def gunicorn_server(self, *args): "Runs Gunicorn server for pseudo-production testing." project = self.cfg['django']['PROJECT_DIR'] args_str = ''.join(args) with cd(self.cfg['django']['DJANGO_ROOT']): flag_str = '%s --access-logfile=- --error-logfile=-' % args_str local('gunicorn %s %s.wsgi:application' % (flag_str, project))
def test(self, *apps): """ Runs py.test for the django project. If <apps> is empty, all tests will be run. Otherwise, tests may be specified in the following way: <appname>[.<search_phrase_1>[.<search_phrase_2>...<search_phrase_n>]]. For example, given an app named "my_app" and a test defined by unittest as "MyTestCase.test_something" the following command would run just that test: "blt django.test my_app.MyTestCase.test_something". To run every test in the test case: "blt django.test my_app.MyTestCase". Or, to run every test for the app: "blt django.test my_app". Specifying multiple apps is also possible: "blt django.test my_app,my_other_app,your_app". However, due to the way test searching is performed by py.test, it is not recommended to use search phrases when testing multiple apps. Usage: blt django.test [apps] Examples: blt django.test - default, runs all tests in the project blt django.test my_app1 my_app2 - runs all tests for my_app1 and my_app2 blt django.test my_app.test_case - runs the test_case in my_app """ project = self.cfg['django']['PROJECT_DIR'] # first clear out any .pyc files. these are cached and could provide # bad test results. example: you import a module in your test case, # but have deleted it on the filesystem. if the .pyc file still # exists, the test will still pass. local('find . -name "*.pyc" -exec rm -f {} \;') test_names = [] app_names = [] flags = [] args = [] for n in apps: if n.startswith('-'): flags.append(n) elif '.' in n: a, t = n.split('.', 1) app_names.append(a) test_names += t.split('.') else: app_names.append(n) args.append(' '.join(flags)) args.append(' '.join(app_names)) args.append('-k "%s"' % ' and '.join(test_names) if len(test_names) else '') command = 'py.test --ds {0}.settings {1}'.format(project, ' '.join(args)).strip() with cd(self.cfg['django']['DJANGO_ROOT']): local(command)
def covrpt(self): """ This will open the html version of the coverage report in your browser. Usage: blt django.covrpt """ project = self.cfg['django']['PROJECT_DIR'] with cd(self.cfg['django']['DJANGO_ROOT']): local('open coverage_html/index.html')
def cov(self, *apps): """ Runs coverage for the django project. Note that this command will run the py.test suite first as it is required for generating the coverage report. If you'd like to filter to a specific set of apps, you can pass it into this command. Usage: blt django.cov [apps] [flags] Examples: blt django.cov - default, runs coverage for the entire project blt django.cov my_app1 my_app2 - runs coverage for my_app1 and my_app2 """ project = self.cfg['django']['PROJECT_DIR'] # first clear out any .pyc files. these are cached and could provide # bad test results. example: you import a module in your test case, # but have deleted it on the filesystem. if the .pyc file still # exists, the test will still pass. local('find . -name "*.pyc" -exec rm -f {} \;') app_names = [] flags = [] pytest_args = [] cov_args = [] for n in apps: if n.startswith('-'): flags.append(n) else: app_names.append(n) # setup py.test args pytest_args = ' '.join(app_names) cov_args = ','.join(app_names) if app_names: cmd = 'coverage run --source {0} {1} -m py.test --ds {2}.settings {3}'.format( cov_args, ' '.join(flags), project, pytest_args).strip() else: cmd = 'coverage run {0} -m py.test --ds {1}.settings'.format( ' '.join(flags), project).strip() with cd(self.cfg['django']['DJANGO_ROOT']): local(cmd) local('coverage report') local('coverage html -d coverage_html')
def status(self, app=None): """ Check the status of outstanding database migrations. Sadly, south gives us no easy api access to get status of what models have changed on an app. It only spits out the summary of changes to stderr and the actual Migration classes to stdout. The status command tries to encapsulate and parse this out to be helpful, but it is obviously brittle and should be tested whenever we upgrade the south library. Args: app: django app to check status for Usage: blt south.status (app) Examples: blt south.status invest - displays status for the invest app """ if not app: msg = ("\nsouth status requires an *app* to check. for example:\n" , " blt south.status my_app\n" , "To check which apps have had model changes run:\n" , " git status | grep models.py") abort("\n".join(msg)) with cd(self.cfg['django']['DJANGO_ROOT']): puts("-- Model Check -----------------------------------------------------------") out = local('python manage.py schemamigration %s --auto --stdout 2>&1' % app, collect_output=True, abort_on_stderr=False) if out.strip() == 'Nothing seems to have changed.': puts("Model is in sync with migrations") else: puts("Model changes found:\n\n%s" % out) puts("\n==> Run `blt south.delta %s` to create a migration set." % app) puts("\n-- Unapplied Migrations --------------------------------------------------") out = local('python manage.py migrate %s --list | grep -v "*" 2>&1' % app, collect_output=True) if out.strip() == app: puts("All migrations have been applied to db") else: puts("Migrations need to be applied to db:\n%s" % out.strip()) puts("\n==> Run `blt south.apply` to push to the database\n")
def runserver(self, ip='127.0.0.1', port='8000'): """ Runs django's development http server Args: ip: the ip address to host on, default is 127.0.0.1 port: the port on host server, default is 8000 Usage: blt django.runserver [ip] [port] Examples: blt django.runserver - default, runs on 127.0.0.1:8000 blt django.runserver 10.1.156.3 - runs on 10.1.156.3:8000 blt django.runserver 10.1.156.3 8888 - runs on 10.1.156.3:8888 """ with cd(self.cfg['django']['DJANGO_ROOT']): print "Setting ASSETS_DEBUG=True" environ['ASSETS_DEBUG'] = "True" local('python manage.py runserver %s:%s' % (ip, port))
def delta(self, *apps): """ Creates a new schema changeset for an app. Unfortunately, south doesn't allow project-wide schemamigrations, you have to provide a specific app name. Args: apps: a list of apps to create changesets for Usage: blt south.delta (apps) Examples: blt south.delta invest payment - creates deltas for invest and payment apps """ if not apps: abort('\n\nPlease provide an app name, e.g. \n blt south.delta my_app') with cd(self.cfg['django']['DJANGO_ROOT']): for app in apps: local('python manage.py schemamigration %s --auto' % app)
def delete(self, deal_slug, database): """ Deletes a deal on the platform. This will completely delete the deal and all related objects on the passed in database. Because we view bundles as atomic units and never overwrite any existing entries on the database, you are required to run this command if you want update anything in the db. Args: deal_slug: the unique slug for the deal database: the database to delete from Usage: blt bundle.delete (deal_slug) (database) Examples: blt bundle.delete blumhouse-productions STAGING """ environ["DATABASE_URL"] = self.cfg["bundle"][database] with cd(self.cfg["django"]["DJANGO_ROOT"]): local("python manage.py bundle delete %s" % (deal_slug))
def shell(self): """Opens a session to django's shell""" with cd(self.cfg['django']['DJANGO_ROOT']): local('python manage.py shell')
def collectstatic(self): """Runs django's collectstatic and the webassets build in one command""" with cd(self.cfg['django']['DJANGO_ROOT']): local('python manage.py collectstatic --noinput') local('python manage.py assets build')