diff --git a/.gitignore b/.gitignore
index c740c14515060fbd08a44785efd8bc9766fb31fc..72cffcc6bc57b982c9ad6d4493543bcdd519a48f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,5 @@ components
 
 *.egg-info
 build
+
+*.pyz
diff --git a/build-validator-pyz.sh b/build-validator-pyz.sh
new file mode 100755
index 0000000000000000000000000000000000000000..dacfb67e0a1d55ca8e6435fffb740add4cf206b7
--- /dev/null
+++ b/build-validator-pyz.sh
@@ -0,0 +1,42 @@
+#! /bin/bash
+
+set -e
+
+rm -Rf pyz-build
+mkdir pyz-build
+
+function pywhichmod()
+{
+  python -c "import $1; import os.path; print($1.__file__.replace('pyc', 'py'))"
+}
+
+function pywhichpkg()
+{
+  python -c "import $1; import os.path; print(os.path.dirname($1.__file__))"
+}
+
+pyzzer.pyz course relate -r \
+  $(pywhichmod six) \
+  $(pywhichpkg markdown) \
+  $(pywhichpkg django) \
+  $(pywhichpkg yaml) \
+  -s '#! /usr/bin/env python2.7' \
+  -o relate-validate.pyz \
+  -x migrations \
+  -x templates \
+  -x 'static/' \
+  -x '\..*\.sw[op]' \
+  -x 'django/db' \
+  -x 'django/contrib' \
+  -x 'django/core/management' \
+  -x 'django/conf/locale' \
+  -x 'django/test' \
+  -x 'django/template' \
+  -x 'django/middleware' \
+  -x 'django/views' \
+  -x 'django/http' \
+  -x 'django/core/serial' \
+  -x 'django/core/mail' \
+  -x '_doctest' \
+  -x '.*~' \
+  -m course.validation:validate_course_on_filesystem_script_entrypoint
diff --git a/course/content.py b/course/content.py
index 9015681eb9f78b9bd49e5f6de40ec522a5e612c4..8e020196b3bfe1a03bd5178ff0371d8c219b0ec1 100644
--- a/course/content.py
+++ b/course/content.py
@@ -32,7 +32,6 @@ import six
 
 from django.utils.timezone import now
 from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
-from django.db import transaction
 
 from markdown.extensions import Extension
 from markdown.treeprocessors import Treeprocessor
@@ -730,15 +729,14 @@ def instantiate_flow_page(location, repo, page_desc, commit_sha):
     return class_(None, location, page_desc)
 
 
-@transaction.atomic
-def adjust_flow_session_page_data(repo, flow_session,
+def _adjust_flow_session_page_data_inner(repo, flow_session,
         course_identifier, flow_desc, commit_sha):
     from course.models import FlowPageData
 
-    id_to_existing_page = {
-            (data.group_id, data.page_id): data
+    id_to_existing_page = dict(
+            ((data.group_id, data.page_id), data)
             for data in FlowPageData.objects.filter(flow_session=flow_session)
-            }
+            )
 
     data = None
 
@@ -779,6 +777,14 @@ def adjust_flow_session_page_data(repo, flow_session,
         flow_session.save()
 
 
+def adjust_flow_session_page_data(repo, flow_session,
+        course_identifier, flow_desc, commit_sha):
+    from django.db import transaction
+    with transaction.atomic():
+        return _adjust_flow_session_page_data_inner(
+                repo, flow_session, course_identifier, flow_desc, commit_sha)
+
+
 def get_course_commit_sha(course, participation):
     sha = course.active_git_commit_sha
 
diff --git a/course/page/text.py b/course/page/text.py
index f66bef9867bd55ca33d6968f4ac384970b2f4c47..b3d84da3eb55bd84cb9b87eece1ec239d151ca39 100644
--- a/course/page/text.py
+++ b/course/page/text.py
@@ -286,6 +286,12 @@ class SymbolicExpressionMatcher(TextAnswerMatcher):
 
         try:
             self.pattern_sym = parse_sympy(pattern)
+        except ImportError:
+            tp, e, _ = sys.exc_info()
+            vctx.add_warning(location, "%s: unable to check "
+                    "symbolic expression (%s: %s)"
+                    % (location, tp.__name__, str(e)))
+
         except:
             tp, e, _ = sys.exc_info()
             raise ValidationError("%s: %s: %s"