Newer
Older
"grade identifier. "
"(Have you renamed the flow? If so, edit the grading "
"opportunity to match.)")
.format(
location=location,
grade_identifier=flow_grade_identifier,
other_flow_id=bad_gopp.flow_id,
new_flow_id=flow_id,
new_grade_identifier=flow_grade_identifier))
# }}}
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
# {{{ check whether page types were changed
def check_for_page_type_changes(vctx, location, course, flow_id, flow_desc):
from course.content import normalize_flow_desc
n_flow_desc = normalize_flow_desc(flow_desc)
from course.models import FlowPageData
for grp in n_flow_desc.groups:
for page_desc in grp.pages:
fpd_with_mismatched_page_types = list(
FlowPageData.objects
.filter(
flow_session__course=course,
flow_session__flow_id=flow_id,
group_id=grp.id,
page_id=page_desc.id)
.exclude(page_type=None)
.exclude(page_type=page_desc.type)
[0:1])
if fpd_with_mismatched_page_types:
mismatched_fpd, = fpd_with_mismatched_page_types
raise ValidationError(
_("%(loc)s, group '%(group)s', page '%(page)s': "
"page type ('%(type_new)s') differs from "
"type used in database ('%(type_old)s')")
% {"loc": location, "group": grp.id,
"page": page_desc.id,
"type_new": page_desc.type,
"type_old": mismatched_fpd.page_type})
# }}}
def validate_course_content(repo, course_file, events_file,
Andreas Klöckner
committed
validate_sha, course=None):
course_desc = get_yaml_from_repo_safely(repo, course_file,
commit_sha=validate_sha)
repo=repo,
commit_sha=validate_sha,
Andreas Klöckner
committed
course=course)
validate_course_desc_struct(vctx, course_file, course_desc)
try:
from course.content import get_yaml_from_repo
events_desc = get_yaml_from_repo(repo, events_file,
commit_sha=validate_sha, cached=False)
Andreas Klöckner
committed
if events_file != "events.yml":
vctx.add_warning(
Andreas Klöckner
committed
_("Events file"),
_("Your course repository does not have an events "
"file named '%s'.")
% events_file)
else:
# That's OK--no calendar info.
pass
validate_calendar_desc_struct(vctx, events_file, events_desc)
check_attributes_yml(
vctx, repo, "", get_repo_blob(repo, "", validate_sha))
try:
flows_tree = get_repo_blob(repo, "media", validate_sha)
except ObjectDoesNotExist:
# That's great--no media directory.
pass
else:
vctx.add_warning(
'media/', _(
"Your course repository has a 'media/' directory. "
"Linking to media files using 'media:' is discouraged. "
"Use the 'repo:' and 'repocur:' linkng schemes instead."))
try:
flows_tree = get_repo_blob(repo, "flows", validate_sha)
except ObjectDoesNotExist:
# That's OK--no flows yet.
pass
else:
used_grade_identifiers = set()
for entry in flows_tree.items():
entry_path = entry.path.decode("utf-8")
if not entry_path.endswith(".yml"):
from course.constants import FLOW_ID_REGEX
match = re.match("^"+FLOW_ID_REGEX+"$", flow_id)
if match is None:
raise ValidationError(
string_concat("%s: ",
_("invalid flow name. "
"Flow names may only contain (roman) "
"letters, numbers, "
flow_desc = get_yaml_from_repo_safely(repo, location,
validate_flow_desc(vctx, location, flow_desc)
# {{{ check grade_identifier
Andreas Klöckner
committed
flow_grade_identifier = None
Andreas Klöckner
committed
flow_grade_identifier = getattr(
flow_desc.rules, "grade_identifier", None)
if (
flow_grade_identifier is not None
and
set([flow_grade_identifier]) & used_grade_identifiers):
Andreas Klöckner
committed
string_concat("%s: ",
_("flow uses the same grade_identifier "
Andreas Klöckner
committed
"as another flow"))
% location)
used_grade_identifiers.add(flow_grade_identifier)
if (course is not None
and flow_grade_identifier is not None):
check_grade_identifier_link(
vctx, location, course, flow_id, flow_grade_identifier)
Andreas Klöckner
committed
# }}}
if course is not None:
check_for_page_type_changes(
vctx, location, course, flow_id, flow_desc)
Andreas Klöckner
committed
# {{{ validation script support
class FileSystemFakeRepo(object):
def __init__(self, root):
self.root = root
def controldir(self):
return self.root
def __getitem__(self, sha):
return sha
def __str__(self):
return "<FAKEREPO:%s>" % self.root
def decode(self):
return self
@property
def tree(self):
return FileSystemFakeRepoTree(self.root)
class FileSystemFakeRepoTreeEntry(object):
class FileSystemFakeRepoTree(object):
def __init__(self, root):
self.root = root
def __getitem__(self, name):
from os.path import join, isdir, exists
name = join(self.root, name)
if not exists(name):
# returns mode, "sha"
if isdir(name):
return None, FileSystemFakeRepoTree(name)
else:
return None, FileSystemFakeRepoFile(name)
def items(self):
import os
return [
FileSystemFakeRepoTreeEntry(
path=n,
mode=os.stat(os.path.join(self.root, n)).st_mode)
for n in os.listdir(self.root)]
class FileSystemFakeRepoFile(object):
def __init__(self, name):
self.name = name
@property
def data(self):
with open(self.name, "rb") as inf:
return inf.read()
def validate_course_on_filesystem_script_entrypoint():
Andreas Klöckner
committed
from django.conf import settings
settings.configure(DEBUG=True)
import django
django.setup()
import os
import argparse
parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument("--course-file", default="course.yml")
parser.add_argument("--events-file", default="events.yml")
parser.add_argument('root', default=os.getcwd())
args = parser.parse_args()
fake_repo = FileSystemFakeRepo(args.root)
warnings = validate_course_content(
fake_repo,
args.course_file, args.events_file,
Andreas Klöckner
committed
validate_sha=fake_repo, course=None)
print(_("WARNINGS: "))
for w in warnings:
print("***", w.location, w.text)