Newer
Older
loc = path + "/" + ".attributes.yml"
Andreas Klöckner
committed
att_roles = ["public", "in_exam", "student", "ta",
"unenrolled", "instructor"]
validate_struct(vctx, loc, att_yml,
required_attrs=[],
allowed_attrs=[(role, list) for role in att_roles])
if hasattr(att_yml, "public"):
vctx.add_warning(loc,
_("Access class 'public' is deprecated. Use 'unenrolled' "
"instead."))
if hasattr(att_yml, "public") and hasattr(att_yml, "unenrolled"):
raise ValidationError(
_("%s: access classes 'public' and 'unenrolled' may not "
"exist simultaneously.")
% (loc))
Andreas Klöckner
committed
for access_kind in att_roles:
Andreas Klöckner
committed
for i, l in enumerate(getattr(att_yml, access_kind)):
Andreas Klöckner
committed
"%s: entry %d in '%s' is not a string"
% (loc, i+1, access_kind))
import stat
for entry in tree.items():
if stat.S_ISDIR(entry.mode):
dummy, blob_sha = tree[entry.path]
Andreas Klöckner
committed
check_attributes_yml(vctx, repo,
path+"/"+entry.path.decode("utf-8"), subtree)
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
# {{{ check whether flow grade identifiers were changed in sketchy ways
def check_grade_identifier_link(
vctx, location, course, flow_id, flow_grade_identifier):
from course.models import GradingOpportunity
for bad_gopp in (
GradingOpportunity.objects
.filter(
course=course,
identifier=flow_grade_identifier)
.exclude(flow_id=flow_id)):
# 0 or 1 trips through this loop because of uniqueness
raise ValidationError(
_(
"{location}: existing grading opportunity with identifier "
"'{grade_identifier}' refers to flow '{other_flow_id}', however "
"flow code in this flow ('{new_flow_id}') specifies the same "
"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))
# }}}
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
# {{{ 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):
repo=repo,
commit_sha=validate_sha,
Andreas Klöckner
committed
course=course)
course_desc = get_yaml_from_repo_safely(repo, course_file,
commit_sha=validate_sha)
validate_staticpage_desc(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)
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
# }}}
# {{{ static pages
try:
pages_tree = get_repo_blob(repo, "staticpages", validate_sha)
except ObjectDoesNotExist:
# That's OK--no flows yet.
pass
else:
for entry in pages_tree.items():
entry_path = entry.path.decode("utf-8")
if not entry_path.endswith(".yml"):
continue
from course.constants import STATICPAGE_PATH_REGEX
page_name = entry_path[:-4]
match = re.match("^"+STATICPAGE_PATH_REGEX+"$", page_name)
if match is None:
raise ValidationError(
string_concat("%s: ",
_(
"invalid page name. "
"Page names may only contain "
"alphanumeric characters (any language) "
"and hyphens."
))
% entry_path)
location = "staticpages/%s" % entry_path
page_desc = get_yaml_from_repo_safely(repo, location,
commit_sha=validate_sha)
validate_staticpage_desc(vctx, location, page_desc)
# }}}
Andreas Klöckner
committed
# {{{ validation script support
class FileSystemFakeRepo(object):
def __init__(self, root):
self.root = root
assert isinstance(self.root, six.binary_type)
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
assert isinstance(self.root, six.binary_type)
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.encode("utf-8"))
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)