コード例 #1
0
ファイル: versioning.py プロジェクト: akiyoko/relate
def run_course_update_command(
        request, repo, content_repo, pctx, command, new_sha, may_update,
        prevent_discarding_revisions):
    if command.startswith("fetch"):
        if command != "fetch":
            command = command[6:]

        if not pctx.course.git_source:
            raise RuntimeError(_("no git source URL specified"))

        client, remote_path = \
            get_dulwich_client_and_remote_path_from_course(pctx.course)

        remote_refs = client.fetch(remote_path, repo)
        transfer_remote_refs(repo, remote_refs)
        remote_head = remote_refs[b"HEAD"]
        if (
                prevent_discarding_revisions
                and
                is_parent_commit(repo, repo[remote_head], repo[b"HEAD"],
                    max_history_check_size=20)):
            raise RuntimeError(_("fetch would discard commits, refusing"))

        repo[b"HEAD"] = remote_head

        messages.add_message(request, messages.SUCCESS, _("Fetch successful."))

        new_sha = remote_head

    if command == "fetch":
        return

    if command == "end_preview":
        messages.add_message(request, messages.INFO,
                _("Preview ended."))
        pctx.participation.preview_git_commit_sha = None
        pctx.participation.save()

        return

    # {{{ validate

    from course.validation import validate_course_content, ValidationError
    try:
        warnings = validate_course_content(
                content_repo, pctx.course.course_file, pctx.course.events_file,
                new_sha, course=pctx.course)
    except ValidationError as e:
        messages.add_message(request, messages.ERROR,
                _("Course content did not validate successfully. (%s) "
                "Update not applied.") % str(e))
        return

    else:
        if not warnings:
            messages.add_message(request, messages.SUCCESS,
                    _("Course content validated successfully."))
        else:
            messages.add_message(request, messages.WARNING,
                    string_concat(
                        _("Course content validated OK, with warnings: "),
                        "<ul>%s</ul>")
                    % ("".join(
                        "<li><i>%(location)s</i>: %(warningtext)s</li>"
                        % {'location': w.location, 'warningtext': w.text}
                        for w in warnings)))

    # }}}

    if command == "preview":
        messages.add_message(request, messages.INFO,
                _("Preview activated."))

        pctx.participation.preview_git_commit_sha = new_sha.decode()
        pctx.participation.save()

    elif command == "update" and may_update:
        pctx.course.active_git_commit_sha = new_sha.decode()
        pctx.course.save()

        messages.add_message(request, messages.SUCCESS,
                _("Update applied. "))

    else:
        raise RuntimeError(_("invalid command"))
コード例 #2
0
ファイル: versioning.py プロジェクト: alexislitool/courseflow
def fetch_course_updates_inner(pctx):
    import sys

    if pctx.role != participation_role.instructor:
        raise PermissionDenied("must be instructor to fetch revisisons")

    form = GitFetchForm(pctx.request.POST, pctx.request.FILES)
    if pctx.request.method == "POST":
        if form.is_valid():
            was_successful = True
            log_lines = []
            try:
                repo = pctx.repo

                if not pctx.course.git_source:
                    raise RuntimeError("no git source URL specified")

                log_lines.append("Pre-fetch head is at '%s'" % repo.head())

                client, remote_path = \
                    get_dulwich_client_and_remote_path_from_course(pctx.course)

                remote_refs = client.fetch(remote_path, repo)
                remote_head = remote_refs["HEAD"]
                if is_parent_commit(repo, repo[remote_head], repo["HEAD"],
                        max_history_check_size=10):
                    raise RuntimeError("fetch would discard commits, refusing")

                repo["HEAD"] = remote_head

                log_lines.append("Post-fetch head is at '%s'" % repo.head())

            except Exception:
                was_successful = False
                from traceback import format_exception
                log = "\n".join(log_lines) + "".join(
                        format_exception(*sys.exc_info()))
            else:
                log = "\n".join(log_lines)

            if was_successful:
                messages.add_message(pctx.request, messages.SUCCESS,
                        "Fetch successful.")
                return redirect(
                        "course.versioning.update_course",
                        pctx.course.identifier)

            return render_course_page(pctx, 'course/course-bulk-result.html', {
                "process_description": "Fetch course updates via git",
                "log": log,
                "status": "Fetch failed. See above for error.",
                "was_successful": was_successful,
                })
        else:
            form = GitFetchForm()
    else:
        form = GitFetchForm()

    return render_course_page(pctx, "course/generic-course-form.html", {
        "form": form,
        "form_description": "Fetch New Course Revisions",
    })
コード例 #3
0
ファイル: versioning.py プロジェクト: akiyoko/relate
def set_up_new_course(request):
    if not request.user.is_staff:
        raise PermissionDenied(_("only staff may create courses"))

    if request.method == "POST":
        form = CourseCreationForm(request.POST)

        if form.is_valid():
            new_course = form.save(commit=False)

            from course.content import get_course_repo_path
            repo_path = get_course_repo_path(new_course)

            try:
                import os
                os.makedirs(repo_path)

                repo = None

                try:
                    with transaction.atomic():
                        from dulwich.repo import Repo
                        repo = Repo.init(repo_path)

                        client, remote_path = \
                            get_dulwich_client_and_remote_path_from_course(
                                    new_course)

                        remote_refs = client.fetch(remote_path, repo)
                        transfer_remote_refs(repo, remote_refs)
                        new_sha = repo[b"HEAD"] = remote_refs[b"HEAD"]

                        vrepo = repo
                        if new_course.course_root_path:
                            from course.content import SubdirRepoWrapper
                            vrepo = SubdirRepoWrapper(
                                    vrepo, new_course.course_root_path)

                        from course.validation import validate_course_content
                        validate_course_content(
                                vrepo, new_course.course_file,
                                new_course.events_file, new_sha)

                        del repo
                        del vrepo

                        new_course.active_git_commit_sha = new_sha.decode()
                        new_course.save()

                        # {{{ set up a participation for the course creator

                        part = Participation()
                        part.user = request.user
                        part.course = new_course
                        part.role = participation_role.instructor
                        part.status = participation_status.active
                        part.save()

                        # }}}

                        messages.add_message(request, messages.INFO,
                                _("Course content validated, creation "
                                "succeeded."))
                except:
                    # Don't coalesce this handler with the one below. We only want
                    # to delete the directory if we created it. Trust me.

                    # Work around read-only files on Windows.
                    # https://docs.python.org/3.5/library/shutil.html#rmtree-example

                    import os
                    import stat
                    import shutil

                    # Make sure files opened for 'repo' above are actually closed.
                    if repo is not None:  # noqa
                        repo.close()  # noqa

                    def remove_readonly(func, path, _):  # noqa
                        "Clear the readonly bit and reattempt the removal"
                        os.chmod(path, stat.S_IWRITE)
                        func(path)

                    try:
                        shutil.rmtree(repo_path, onerror=remove_readonly)
                    except OSError:
                        messages.add_message(request, messages.WARNING,
                                ugettext("Failed to delete unused "
                                "repository directory '%s'.")
                                % repo_path)

                    raise

            except Exception as e:
                from traceback import print_exc
                print_exc()

                messages.add_message(request, messages.ERROR,
                        string_concat(
                            _("Course creation failed"),
                            ": %(err_type)s: %(err_str)s")
                        % {"err_type": type(e).__name__,
                            "err_str": str(e)})
            else:
                return redirect(
                        "relate-course_page",
                        new_course.identifier)

    else:
        form = CourseCreationForm()

    return render(request, "generic-form.html", {
        "form_description": _("Set up new course"),
        "form": form
        })
コード例 #4
0
ファイル: versioning.py プロジェクト: samtux/relate-theme
def run_course_update_command(request, repo, content_repo, pctx, command,
                              new_sha, may_update,
                              prevent_discarding_revisions):
    if command.startswith("fetch"):
        if command != "fetch":
            command = command[6:]

        if not pctx.course.git_source:
            raise RuntimeError(_("no git source URL specified"))

        client, remote_path = \
            get_dulwich_client_and_remote_path_from_course(pctx.course)

        remote_refs = client.fetch(remote_path, repo)
        transfer_remote_refs(repo, remote_refs)
        remote_head = remote_refs[b"HEAD"]
        if (prevent_discarding_revisions and is_parent_commit(
                repo, repo[remote_head], repo[b"HEAD"],
                max_history_check_size=20)):
            raise RuntimeError(_("fetch would discard commits, refusing"))

        repo[b"HEAD"] = remote_head

        messages.add_message(request, messages.SUCCESS, _("Fetch successful."))

        new_sha = remote_head

    if command == "fetch":
        return

    if command == "end_preview":
        pctx.participation.preview_git_commit_sha = None
        pctx.participation.save()

        messages.add_message(request, messages.INFO, _("Preview ended."))

        return

    # {{{ validate

    from course.validation import validate_course_content, ValidationError
    try:
        warnings = validate_course_content(content_repo,
                                           pctx.course.course_file,
                                           pctx.course.events_file,
                                           new_sha,
                                           course=pctx.course)
    except ValidationError as e:
        messages.add_message(
            request, messages.ERROR,
            _("Course content did not validate successfully. (%s) "
              "Update not applied.") % str(e))
        return

    else:
        if not warnings:
            messages.add_message(request, messages.SUCCESS,
                                 _("Course content validated successfully."))
        else:
            messages.add_message(
                request, messages.WARNING,
                string_concat(
                    _("Course content validated OK, with warnings: "),
                    "<ul>%s</ul>") %
                ("".join("<li><i>%(location)s</i>: %(warningtext)s</li>" % {
                    'location': w.location,
                    'warningtext': w.text
                } for w in warnings)))

    # }}}

    if command == "preview":
        messages.add_message(request, messages.INFO, _("Preview activated."))

        pctx.participation.preview_git_commit_sha = new_sha.decode()
        pctx.participation.save()

    elif command == "update" and may_update:
        pctx.course.active_git_commit_sha = new_sha.decode()
        pctx.course.save()

        if pctx.participation.preview_git_commit_sha is not None:
            pctx.participation.preview_git_commit_sha = None
            pctx.participation.save()

            messages.add_message(request, messages.INFO, _("Preview ended."))

        messages.add_message(request, messages.SUCCESS, _("Update applied. "))

    else:
        raise RuntimeError(_("invalid command"))
コード例 #5
0
ファイル: versioning.py プロジェクト: alexislitool/courseflow
def set_up_new_course(request):
    if not request.user.is_staff:
        raise PermissionDenied("only staff may create courses")

    if request.method == "POST":
        form = CourseCreationForm(request.POST)

        if form.is_valid():
            new_course = form.save(commit=False)

            from course.content import get_course_repo_path
            repo_path = get_course_repo_path(new_course)

            try:
                import os
                os.makedirs(repo_path)

                try:
                    with transaction.atomic():
                        from dulwich.repo import Repo
                        repo = Repo.init(repo_path)

                        client, remote_path = \
                            get_dulwich_client_and_remote_path_from_course(
                                    new_course)

                        remote_refs = client.fetch(remote_path, repo)
                        new_sha = repo["HEAD"] = remote_refs["HEAD"]

                        from course.validation import validate_course_content
                        validate_course_content(
                                repo, new_course.course_file,
                                new_course.events_file, new_sha)

                        new_course.valid = True
                        new_course.active_git_commit_sha = new_sha
                        new_course.save()

                        # {{{ set up a participation for the course creator

                        part = Participation()
                        part.user = request.user
                        part.course = new_course
                        part.role = participation_role.instructor
                        part.status = participation_status.active
                        part.save()

                        # }}}

                        messages.add_message(request, messages.INFO,
                                "Course content validated, creation succeeded. "
                                "You may want to view the events used "
                                "in the course content and create them. "
                                + '<a href="%s" class="btn btn-primary">'
                                'Check &raquo;</a>'
                                % reverse("course.calendar.check_events",
                                    args=(new_course.identifier,)))
                except:
                    # Don't coalesce this handler with the one below. We only want
                    # to delete the directory if we created it. Trust me.
                    import shutil
                    shutil.rmtree(repo_path)
                    raise

            except Exception as e:
                from traceback import print_exc
                print_exc()

                messages.add_message(request, messages.ERROR,
                        "Course creation failed: %s: %s" % (
                            type(e).__name__, str(e)))
            else:
                return redirect(
                        "course.views.course_page",
                        new_course.identifier)

    else:
        form = CourseCreationForm()

    return render(request, "generic-form.html", {
        "form_description": "Set up new course",
        "form": form
        })
コード例 #6
0
ファイル: versioning.py プロジェクト: samtux/relate-theme
def set_up_new_course(request):
    # type: (http.HttpRequest) -> http.HttpResponse
    if request.method == "POST":
        form = CourseCreationForm(request.POST)

        if form.is_valid():
            new_course = form.save(commit=False)

            from course.content import get_course_repo_path
            repo_path = get_course_repo_path(new_course)

            try:
                import os
                os.makedirs(repo_path)

                repo = None

                try:
                    with transaction.atomic():
                        repo = Repo.init(repo_path)

                        client, remote_path = \
                            get_dulwich_client_and_remote_path_from_course(
                                    new_course)

                        remote_refs = client.fetch(remote_path, repo)
                        if remote_refs is None:
                            raise RuntimeError(
                                _("No refs found in remote repository"
                                  " (i.e. no master branch, no HEAD). "
                                  "This looks very much like a blank repository. "
                                  "Please create course.yml in the remote "
                                  "repository before creating your course."))

                        transfer_remote_refs(repo, remote_refs)
                        new_sha = repo[b"HEAD"] = remote_refs[b"HEAD"]

                        vrepo = repo
                        if new_course.course_root_path:
                            from course.content import SubdirRepoWrapper
                            vrepo = SubdirRepoWrapper(
                                vrepo, new_course.course_root_path)

                        from course.validation import validate_course_content
                        validate_course_content(  # type: ignore
                            vrepo, new_course.course_file,
                            new_course.events_file, new_sha)

                        del vrepo

                        new_course.active_git_commit_sha = new_sha.decode()
                        new_course.save()

                        # {{{ set up a participation for the course creator

                        part = Participation()
                        part.user = request.user
                        part.course = new_course
                        part.status = participation_status.active
                        part.save()

                        part.roles.set([
                            # created by signal handler for course creation
                            ParticipationRole.objects.get(
                                course=new_course, identifier="instructor")
                        ])

                        # }}}

                        messages.add_message(
                            request, messages.INFO,
                            _("Course content validated, creation "
                              "succeeded."))
                except:
                    # Don't coalesce this handler with the one below. We only want
                    # to delete the directory if we created it. Trust me.

                    # Work around read-only files on Windows.
                    # https://docs.python.org/3.5/library/shutil.html#rmtree-example

                    import os
                    import stat
                    import shutil

                    # Make sure files opened for 'repo' above are actually closed.
                    if repo is not None:  # noqa
                        repo.close()  # noqa

                    def remove_readonly(func, path, _):  # noqa
                        "Clear the readonly bit and reattempt the removal"
                        os.chmod(path, stat.S_IWRITE)
                        func(path)

                    try:
                        shutil.rmtree(repo_path, onerror=remove_readonly)
                    except OSError:
                        messages.add_message(
                            request, messages.WARNING,
                            ugettext("Failed to delete unused "
                                     "repository directory '%s'.") % repo_path)

                    raise

            except Exception as e:
                from traceback import print_exc
                print_exc()

                messages.add_message(
                    request, messages.ERROR,
                    string_concat(_("Course creation failed"),
                                  ": %(err_type)s: %(err_str)s") % {
                                      "err_type": type(e).__name__,
                                      "err_str": str(e)
                                  })
            else:
                return redirect("relate-course_page", new_course.identifier)

    else:
        form = CourseCreationForm()

    return render(request, "generic-form.html", {
        "form_description": _("Set up new course"),
        "form": form
    })
コード例 #7
0
ファイル: versioning.py プロジェクト: lacrosse91/relate
def run_course_update_command(request, repo, content_repo, pctx, command,
                              new_sha, may_update,
                              prevent_discarding_revisions):
    if command not in ALLOWED_COURSE_REVISIOIN_COMMANDS:
        raise RuntimeError(_("invalid command"))

    if command.startswith("fetch"):
        if command != "fetch":
            command = command[6:]

        client, remote_path = \
            get_dulwich_client_and_remote_path_from_course(pctx.course)

        fetch_pack_result = client.fetch(remote_path, repo)

        assert isinstance(fetch_pack_result, dulwich.client.FetchPackResult)

        transfer_remote_refs(repo, fetch_pack_result)
        remote_head = fetch_pack_result.refs[b"HEAD"]
        if prevent_discarding_revisions:
            # Guard against bad scenario:
            # Local is not ancestor of remote, i.e. the branches have diverged.
            if not is_ancestor_commit(repo, repo[b"HEAD"], repo[remote_head],
                    max_history_check_size=20) and \
                    repo[b"HEAD"] != repo[remote_head]:
                raise RuntimeError(
                    _("internal git repo has more commits. Fetch, "
                      "merge and push."))

        repo[b"HEAD"] = remote_head

        messages.add_message(request, messages.SUCCESS, _("Fetch successful."))

        new_sha = remote_head

    if command == "fetch":
        return

    if command == "end_preview":
        pctx.participation.preview_git_commit_sha = None
        pctx.participation.save()

        messages.add_message(request, messages.INFO, _("Preview ended."))

        return

    # {{{ validate

    from course.validation import validate_course_content, ValidationError
    try:
        warnings = validate_course_content(content_repo,
                                           pctx.course.course_file,
                                           pctx.course.events_file,
                                           new_sha,
                                           course=pctx.course)
    except ValidationError as e:
        messages.add_message(
            request, messages.ERROR,
            _("Course content did not validate successfully: '%s' "
              "Update not applied.") % str(e))
        return

    else:
        if not warnings:
            messages.add_message(request, messages.SUCCESS,
                                 _("Course content validated successfully."))
        else:
            messages.add_message(
                request, messages.WARNING,
                string_concat(
                    _("Course content validated OK, with warnings: "),
                    "<ul>%s</ul>") %
                ("".join("<li><i>%(location)s</i>: %(warningtext)s</li>" % {
                    'location': w.location,
                    'warningtext': w.text
                } for w in warnings)))

    # }}}

    if command == "preview":
        messages.add_message(request, messages.INFO, _("Preview activated."))

        pctx.participation.preview_git_commit_sha = new_sha.decode()
        pctx.participation.save()

    elif command == "update" and may_update:  # pragma: no branch
        pctx.course.active_git_commit_sha = new_sha.decode()
        pctx.course.save()

        if pctx.participation.preview_git_commit_sha is not None:
            pctx.participation.preview_git_commit_sha = None
            pctx.participation.save()

            messages.add_message(request, messages.INFO, _("Preview ended."))

        messages.add_message(request, messages.SUCCESS, _("Update applied. "))
コード例 #8
0
def run_course_update_command(request, pctx, command, new_sha):
    if command.startswith("fetch_"):
        command = command[6:]

        if not pctx.course.git_source:
            raise RuntimeError("no git source URL specified")

        repo = pctx.repo

        client, remote_path = \
            get_dulwich_client_and_remote_path_from_course(pctx.course)

        remote_refs = client.fetch(remote_path, repo)
        remote_head = remote_refs["HEAD"]
        if is_parent_commit(repo, repo[remote_head], repo["HEAD"],
                max_history_check_size=10):
            raise RuntimeError("fetch would discard commits, refusing")

        repo["HEAD"] = remote_head

        messages.add_message(request, messages.SUCCESS, "Fetch successful.")

        new_sha = repo.head()

    if command == "end_preview":
        messages.add_message(request, messages.INFO,
                "Preview ended.")
        pctx.participation.preview_git_commit_sha = None
        pctx.participation.save()

        return

    # {{{ validate

    from course.validation import validate_course_content, ValidationError
    try:
        warnings = validate_course_content(
                repo, pctx.course.course_file, pctx.course.events_file, new_sha)
    except ValidationError as e:
        messages.add_message(request, messages.ERROR,
                "Course content did not validate successfully. (%s) "
                "Update not applied." % str(e))
        return

    else:
        if not warnings:
            messages.add_message(request, messages.SUCCESS,
                    "Course content validated successfully.")
        else:
            messages.add_message(request, messages.WARNING,
                    "Course content validated OK, with warnings:"
                    "<ul>%s</ul>"
                    % ("".join(
                        "<li><i>%s</i>: %s</li>" % (w.location, w.text)
                        for w in warnings)))

    # }}}

    if command == "preview":
        messages.add_message(request, messages.INFO,
                "Preview activated.")

        pctx.participation.preview_git_commit_sha = new_sha
        pctx.participation.save()

    elif command == "update":
        pctx.course.active_git_commit_sha = new_sha
        pctx.course.valid = True
        pctx.course.save()

        messages.add_message(request, messages.SUCCESS,
                "Update applied. "
                "You may want to view the events used "
                "in the course content and check that they "
                "are recognized. "
                + '<p><a href="%s" class="btn btn-primary" '
                'style="margin-top:8px">'
                'Check &raquo;</a></p>'
                % reverse("course.calendar.check_events",
                    args=(pctx.course.identifier,)))

    else:
        raise RuntimeError("invalid command")
コード例 #9
0
def set_up_new_course(request):
    if not request.user.is_staff:
        raise PermissionDenied("only staff may create courses")

    if request.method == "POST":
        form = CourseCreationForm(request.POST)

        if form.is_valid():
            new_course = form.save(commit=False)

            from course.content import get_course_repo_path
            repo_path = get_course_repo_path(new_course)

            try:
                import os
                os.makedirs(repo_path)

                try:
                    with transaction.atomic():
                        from dulwich.repo import Repo
                        repo = Repo.init(repo_path)

                        client, remote_path = \
                            get_dulwich_client_and_remote_path_from_course(
                                    new_course)

                        remote_refs = client.fetch(remote_path, repo)
                        new_sha = repo["HEAD"] = remote_refs["HEAD"]

                        from course.validation import validate_course_content
                        validate_course_content(
                                repo, new_course.course_file,
                                new_course.events_file, new_sha)

                        new_course.valid = True
                        new_course.active_git_commit_sha = new_sha
                        new_course.save()

                        # {{{ set up a participation for the course creator

                        part = Participation()
                        part.user = request.user
                        part.course = new_course
                        part.role = participation_role.instructor
                        part.status = participation_status.active
                        part.save()

                        # }}}

                        messages.add_message(request, messages.INFO,
                                "Course content validated, creation succeeded. "
                                "You may want to view the events used "
                                "in the course content and create them. "
                                + '<a href="%s" class="btn btn-primary">'
                                'Check &raquo;</a>'
                                % reverse("course.calendar.check_events",
                                    args=(new_course.identifier,)))
                except:
                    # Don't coalesce this handler with the one below. We only want
                    # to delete the directory if we created it. Trust me.
                    import shutil
                    shutil.rmtree(repo_path)
                    raise

            except Exception as e:
                from traceback import print_exc
                print_exc()

                messages.add_message(request, messages.ERROR,
                        "Course creation failed: %s: %s" % (
                            type(e).__name__, str(e)))
            else:
                return redirect(
                        "course.views.course_page",
                        new_course.identifier)

    else:
        form = CourseCreationForm()

    return render(request, "generic-form.html", {
        "form_description": "Set up new course",
        "form": form
        })
コード例 #10
0
def set_up_new_course(request):
    if not request.user.is_staff:
        raise PermissionDenied(_("only staff may create courses"))

    if request.method == "POST":
        form = CourseCreationForm(request.POST)

        if form.is_valid():
            new_course = form.save(commit=False)

            from course.content import get_course_repo_path
            repo_path = get_course_repo_path(new_course)

            try:
                import os
                os.makedirs(repo_path)

                try:
                    with transaction.atomic():
                        from dulwich.repo import Repo
                        repo = Repo.init(repo_path)

                        client, remote_path = \
                            get_dulwich_client_and_remote_path_from_course(
                                    new_course)

                        remote_refs = client.fetch(remote_path, repo)
                        new_sha = repo["HEAD"] = remote_refs["HEAD"]

                        vrepo = repo
                        if new_course.course_root_path:
                            from course.content import SubdirRepoWrapper
                            vrepo = SubdirRepoWrapper(
                                vrepo, new_course.course_root_path)

                        from course.validation import validate_course_content
                        validate_course_content(vrepo, new_course.course_file,
                                                new_course.events_file,
                                                new_sha)

                        del repo
                        del vrepo

                        new_course.valid = True
                        new_course.active_git_commit_sha = new_sha
                        new_course.save()

                        # {{{ set up a participation for the course creator

                        part = Participation()
                        part.user = request.user
                        part.course = new_course
                        part.role = participation_role.instructor
                        part.status = participation_status.active
                        part.save()

                        # }}}

                        messages.add_message(
                            request, messages.INFO,
                            _("Course content validated, creation "
                              "succeeded."))
                except:
                    # Don't coalesce this handler with the one below. We only want
                    # to delete the directory if we created it. Trust me.

                    # Work around read-only files on Windows.
                    # https://docs.python.org/3.5/library/shutil.html#rmtree-example

                    import os
                    import stat
                    import shutil

                    # Make sure files opened for 'repo' above are actually closed.
                    import gc
                    gc.collect()

                    def remove_readonly(func, path, _):  # noqa
                        "Clear the readonly bit and reattempt the removal"
                        os.chmod(path, stat.S_IWRITE)
                        func(path)

                    try:
                        shutil.rmtree(repo_path, onerror=remove_readonly)
                    except OSError:
                        messages.add_message(
                            request, messages.WARNING,
                            ugettext("Failed to delete unused "
                                     "repository directory '%s'.") % repo_path)

                    raise

            except Exception as e:
                from traceback import print_exc
                print_exc()

                messages.add_message(
                    request, messages.ERROR,
                    string_concat(_("Course creation failed"),
                                  ": %(err_type)s: %(err_str)s") % {
                                      "err_type": type(e).__name__,
                                      "err_str": str(e)
                                  })
            else:
                return redirect("relate-course_page", new_course.identifier)

    else:
        form = CourseCreationForm()

    return render(request, "generic-form.html", {
        "form_description": _("Set up new course"),
        "form": form
    })