コード例 #1
0
ファイル: configure.py プロジェクト: mtthwhggns/a-plus
def configure_content(instance, url):
    """
    Configures course content by trusted remote URL.
    """
    if not url:
        return [_("Configuration URL required.")]
    try:
        url = url.strip()
        response = requests.get(url)
    except Exception as e:
        return [
            _("Request for a course configuration failed with error '{error!s}'. "
              "Configuration of course aborted.").format(error=e)
        ]

    instance.configure_url = url
    instance.save()

    try:
        config = json.loads(response.text)
    except Exception as e:
        return [
            _("JSON parser raised error '{error!s}'. "
              "Configuration of course aborted.").format(error=e)
        ]

    errors = []

    # Configure course instance attributes.
    if "start" in config:
        dt = parse_date(config["start"], errors)
        if dt:
            instance.starting_time = dt
    if "end" in config:
        dt = parse_date(config["end"], errors)
        if dt:
            instance.ending_time = dt
    if "enrollment_start" in config:
        instance.enrollment_starting_time = parse_date(
            config["enrollment_start"], errors, allow_null=True)
    if "enrollment_end" in config:
        instance.enrollment_ending_time = parse_date(config["enrollment_end"],
                                                     errors,
                                                     allow_null=True)
    if "lifesupport_time" in config:
        instance.lifesupport_time = parse_date(config["lifesupport_time"],
                                               errors,
                                               allow_null=True)
    if "archive_time" in config:
        instance.archive_time = parse_date(config["archive_time"],
                                           errors,
                                           allow_null=True)
    if "enrollment_audience" in config:
        enroll_audience = parse_choices(
            config["enrollment_audience"], {
                'internal': CourseInstance.ENROLLMENT_AUDIENCE.INTERNAL_USERS,
                'external': CourseInstance.ENROLLMENT_AUDIENCE.EXTERNAL_USERS,
                'all': CourseInstance.ENROLLMENT_AUDIENCE.ALL_USERS,
            }, "enrollment_audience", errors)
        if enroll_audience is not None:
            instance.enrollment_audience = enroll_audience
    if "view_content_to" in config:
        view_content_to = parse_choices(
            config["view_content_to"], {
                'enrolled': CourseInstance.VIEW_ACCESS.ENROLLED,
                'enrollment_audience':
                CourseInstance.VIEW_ACCESS.ENROLLMENT_AUDIENCE,
                'all_registered': CourseInstance.VIEW_ACCESS.ALL_REGISTERED,
                'public': CourseInstance.VIEW_ACCESS.PUBLIC,
            }, "view_content_to", errors)
        if view_content_to is not None:
            instance.view_content_to = view_content_to
    if "index_mode" in config:
        index_mode = parse_choices(
            config["index_mode"], {
                'results': CourseInstance.INDEX_TYPE.RESULTS,
                'toc': CourseInstance.INDEX_TYPE.TOC,
                'last': CourseInstance.INDEX_TYPE.LAST,
                'experimental': CourseInstance.INDEX_TYPE.EXPERIMENT,
            }, "index_mode", errors)
        if index_mode is not None:
            instance.index_mode = index_mode

    numbering_choices = {
        'none': CourseInstance.CONTENT_NUMBERING.NONE,
        'arabic': CourseInstance.CONTENT_NUMBERING.ARABIC,
        'roman': CourseInstance.CONTENT_NUMBERING.ROMAN,
        'hidden': CourseInstance.CONTENT_NUMBERING.HIDDEN,
    }
    if "content_numbering" in config:
        numbering = parse_choices(config["content_numbering"],
                                  numbering_choices, "content_numbering",
                                  errors)
        if numbering is not None:
            instance.content_numbering = numbering
    if "module_numbering" in config:
        numbering = parse_choices(config["module_numbering"],
                                  numbering_choices, "module_numbering",
                                  errors)
        if numbering is not None:
            instance.module_numbering = numbering
    if "course_description" in config:
        # Course index.yaml files have previously used the field "description"
        # for a hidden description, so we use "course_description" for
        # the visible description.
        instance.description = str(config["course_description"])
    if "course_footer" in config:
        instance.footer = str(config["course_footer"])
    if "lang" in config:
        langs = config["lang"]
        if isinstance(langs, list):
            langs = [
                lang for lang in langs if instance.is_valid_language(lang)
            ]
            if langs:
                instance.language = "|{}|".format("|".join(langs))
        elif instance.is_valid_language(langs):
            instance.language = str(langs)[:5]
    if "contact" in config:
        instance.technical_error_emails = str(config["contact"])
    if "head_urls" in config:
        head_urls = config["head_urls"]
        instance.head_urls = "\n".join(head_urls) if isinstance(
            head_urls, list) else str(head_urls)
    if "assistants" in config:
        if not isinstance(config["assistants"], list):
            errors.append(_("Assistants must be given as a student ID array."))
        else:
            assistants = []
            for sid in config["assistants"]:
                try:
                    profile = UserProfile.objects.get(student_id=sid)
                except UserProfile.DoesNotExist as err:
                    errors.append(
                        _("Adding the assistant failed, because an associated "
                          "user with student ID {id} does not exist.").format(
                              id=sid))
                else:
                    assistants.append(profile)
            instance.assistants.set(assistants)
    instance.build_log_url = str(
        config['build_log_url']) if 'build_log_url' in config else ''
    # configure_url excluded from validation because the default Django URL
    # validation does not accept dotless domain names such as "grader"
    instance.full_clean(exclude=['configure_url'])
    instance.save()

    if not "categories" in config or not isinstance(config["categories"],
                                                    dict):
        errors.append(_("Categories required as an object."))
        return errors
    if not "modules" in config or not isinstance(config["modules"], list):
        errors.append(_("Modules required as an array."))
        return errors

    # Configure learning object categories.
    category_map = {}
    seen = []
    for key, c in config.get("categories", {}).items():
        if not "name" in c:
            errors.append(_("Category requires a name."))
            continue
        try:
            category = instance.categories.get(
                name=format_localization(c["name"]))
        except LearningObjectCategory.DoesNotExist:
            category = LearningObjectCategory(course_instance=instance,
                                              name=format_localization(
                                                  c["name"]))
        if "status" in c:
            category.status = str(c["status"])
        if "description" in c:
            category.description = str(c["description"])
        if "points_to_pass" in c:
            i = parse_int(c["points_to_pass"], errors)
            if not i is None:
                category.points_to_pass = i
        for field in [
                "confirm_the_level",
                "accept_unofficial_submits",
        ]:
            if field in c:
                setattr(category, field, parse_bool(c[field]))
        category.full_clean()
        category.save()
        category_map[key] = category
        seen.append(category.id)

    for category in instance.categories.all():
        if not category.id in seen:
            category.status = LearningObjectCategory.STATUS.HIDDEN
            category.save()

    # Configure course modules.
    seen_modules = []
    seen_objects = []
    nn = 0
    n = 0
    for m in config.get("modules", []):
        if not "key" in m:
            errors.append(_("Module requires a key."))
            continue
        try:
            module = instance.course_modules.get(url=str(m["key"]))
        except CourseModule.DoesNotExist:
            module = CourseModule(course_instance=instance, url=str(m["key"]))

        if "order" in m:
            module.order = parse_int(m["order"], errors)
        else:
            n += 1
            module.order = n

        if "title" in m:
            module.name = format_localization(m["title"])
        elif "name" in m:
            module.name = format_localization(m["name"])
        if not module.name:
            module.name = "-"
        if "status" in m:
            module.status = str(m["status"])
        if "points_to_pass" in m:
            i = parse_int(m["points_to_pass"], errors)
            if not i is None:
                module.points_to_pass = i
        if "introduction" in m:
            module.introduction = str(m["introduction"])

        if "open" in m:
            dt = parse_date(m["open"], errors)
            if dt:
                module.opening_time = dt
        if not module.opening_time:
            module.opening_time = instance.starting_time

        if "close" in m:
            dt = parse_date(m["close"], errors)
            if dt:
                module.closing_time = dt
        elif "duration" in m:
            dt = parse_duration(module.opening_time, m["duration"], errors)
            if dt:
                module.closing_time = dt
        if not module.closing_time:
            module.closing_time = instance.ending_time

        if "read-open" in m:
            module.reading_opening_time = parse_date(m["read-open"],
                                                     errors,
                                                     allow_null=True)

        if "late_close" in m:
            dt = parse_date(m["late_close"], errors)
            if dt:
                module.late_submission_deadline = dt
                module.late_submissions_allowed = True
        elif "late_duration" in m:
            dt = parse_duration(module.closing_time, m["late_duration"],
                                errors)
            if dt:
                module.late_submission_deadline = dt
                module.late_submissions_allowed = True
        if "late_penalty" in m:
            f = parse_float(m["late_penalty"], errors)
            if not f is None:
                module.late_submission_penalty = f

        module.full_clean()
        module.save()
        seen_modules.append(module.id)

        if not ("numerate_ignoring_modules" in config \
                and parse_bool(config["numerate_ignoring_modules"])):
            nn = 0
        if "children" in m:
            nn = configure_learning_objects(category_map, module,
                                            m["children"], None, seen_objects,
                                            errors, nn)

    for module in list(instance.course_modules.all()):
        if not module.id in seen_modules:
            module.status = CourseModule.STATUS.HIDDEN
            module.save()
        for lobject in list(module.learning_objects.all()):
            if not lobject.id in seen_objects:
                exercise = lobject.as_leaf_class()
                if (not isinstance(exercise, BaseExercise)
                        or exercise.submissions.count() == 0):
                    exercise.delete()
                else:
                    lobject.status = LearningObject.STATUS.HIDDEN
                    lobject.order = 9999
                    lobject.save()

    # Clean up obsolete categories.
    for category in instance.categories.filter(
            status=LearningObjectCategory.STATUS.HIDDEN):
        if category.learning_objects.count() == 0:
            category.delete()

    return errors
コード例 #2
0
def main():
    from django.contrib.auth.models import User
    from course.models import Course, CourseInstance, CourseModule
    from course.models import Enrollment, StudentGroup, LearningObjectCategory
    from exercise.exercise_models import BaseExercise
    from exercise.submission_models import Submission

    now = timezone.now()
    year_later = now + timedelta(days=365)

    user0 = User(id=500)
    user0.username = '******'
    user0.first_name = 'Perry'
    user0.last_name = 'Cash'
    user0.email = '*****@*****.**'
    user0.set_password('percash0')
    user0.save()

    user1 = User(id=501)
    user1.username = '******'
    user1.first_name = 'Zorita'
    user1.last_name = 'Alston'
    user1.email = '*****@*****.**'
    user1.set_password('zoralst1')
    user1.save()

    user2 = User(id=502)
    user2.username = '******'
    user2.first_name = 'Cameron'
    user2.last_name = 'Stein'
    user2.email = '*****@*****.**'
    user2.set_password('camstei2')
    user2.save()

    user3 = User(id=503)
    user3.username = '******'
    user3.first_name = 'Brynne'
    user3.last_name = 'Pollard'
    user3.email = '*****@*****.**'
    user3.set_password('brypoll3')
    user3.save()

    user4 = User(id=504)
    user4.username = '******'
    user4.first_name = 'Allistair'
    user4.last_name = 'Blackburn'
    user4.email = '*****@*****.**'
    user4.set_password('allblac4')
    user4.save()

    user5 = User(id=505)
    user5.username = '******'
    user5.first_name = 'Zachary'
    user5.last_name = 'Bolton'
    user5.email = '*****@*****.**'
    user5.set_password('zacbolt5')
    user5.save()

    user6 = User(id=506)
    user6.username = '******'
    user6.first_name = 'Kelsie'
    user6.last_name = 'Wolf'
    user6.email = '*****@*****.**'
    user6.set_password('kelwolf6')
    user6.save()

    user7 = User(id=507)
    user7.username = '******'
    user7.first_name = 'John'
    user7.last_name = 'McCarty'
    user7.email = '*****@*****.**'
    user7.set_password('johmcca7')
    user7.save()

    user8 = User(id=508)
    user8.username = '******'
    user8.first_name = 'Sheila'
    user8.last_name = 'Rodriquez'
    user8.email = '*****@*****.**'
    user8.set_password('sherodr8')
    user8.save()

    user9 = User(id=509)
    user9.username = '******'
    user9.first_name = 'Cassady'
    user9.last_name = 'Stanley'
    user9.email = '*****@*****.**'
    user9.set_password('casstan9')
    user9.save()

    course0 = Course()
    course0.name = 'Test Course'
    course0.url = 'test-course'
    course0.save()

    instance0 = CourseInstance(id=100, course=course0)
    instance0.instance_name = 'Test Instance'
    instance0.url = 'test-instance'
    instance0.starting_time = now
    instance0.ending_time = year_later
    instance0.save()

    Enrollment.objects.create(course_instance=instance0, user_profile=user0.userprofile)
    Enrollment.objects.create(course_instance=instance0, user_profile=user1.userprofile)
    Enrollment.objects.create(course_instance=instance0, user_profile=user2.userprofile)
    Enrollment.objects.create(course_instance=instance0, user_profile=user3.userprofile)
    Enrollment.objects.create(course_instance=instance0, user_profile=user4.userprofile)
    Enrollment.objects.create(course_instance=instance0, user_profile=user5.userprofile)
    Enrollment.objects.create(course_instance=instance0, user_profile=user6.userprofile)
    Enrollment.objects.create(course_instance=instance0, user_profile=user7.userprofile)
    Enrollment.objects.create(course_instance=instance0, user_profile=user8.userprofile)
    Enrollment.objects.create(course_instance=instance0, user_profile=user9.userprofile)

    group0 = StudentGroup.objects.create(id=200, course_instance=instance0)
    group0.members.add(user0.userprofile)
    group0.members.add(user1.userprofile)
    group0.save()

    group1 = StudentGroup.objects.create(id=201, course_instance=instance0)
    group1.members.add(user0.userprofile)
    group1.members.add(user2.userprofile)
    group1.members.add(user3.userprofile)
    group1.save()

    group2 = StudentGroup.objects.create(id=202, course_instance=instance0)
    group2.members.add(user1.userprofile)
    group2.members.add(user4.userprofile)
    group2.save()

    group3 = StudentGroup.objects.create(id=203, course_instance=instance0)
    group3.members.add(user5.userprofile)
    group3.members.add(user6.userprofile)
    group3.members.add(user7.userprofile)
    group3.members.add(user8.userprofile)
    group3.save()

    module0 = CourseModule(course_instance=instance0)
    module0.name = "First module"
    module0.url = "first-module"
    module0.opening_time = now
    module0.closing_time = year_later
    module0.save()

    module1 = CourseModule(course_instance=instance0)
    module1.name = "Second module"
    module1.url = "second-module"
    module1.opening_time = now
    module1.closing_time = year_later
    module1.save()

    category0 = LearningObjectCategory(course_instance=instance0)
    category0.name = "Some category"
    category0.save()

    exercise0 = BaseExercise(id=300, course_module=module0, category=category0)
    exercise0.name = "Easy exercise"
    exercise0.url = 'easy-exercise'
    exercise0.max_submissions = 10
    exercise0.max_group_size = 4
    exercise0.max_points = 100
    exercise0.points_to_pass = 50
    exercise0.save()

    exercise1 = BaseExercise(id=301, course_module=module0, category=category0)
    exercise1.name = "Hard exercise"
    exercise1.url = 'hard-exercise'
    exercise1.max_submissions = 5
    exercise0.max_group_size = 2
    exercise1.max_points = 100
    exercise1.points_to_pass = 100
    exercise1.save()

    exercise2 = BaseExercise(id=302, course_module=module1, category=category0)
    exercise2.name = "Nice exercise"
    exercise2.url = 'nice-exercise'
    exercise2.max_submissions = 0
    exercise2.max_points = 10
    exercise2.points_to_pass = 0
    exercise2.save()

    submission0 = Submission.objects.create(id=400, exercise=exercise0)
    submission0.submitters.add(user0.userprofile)
    submission0.submitters.add(user1.userprofile)
    submission0.feedback = '<html><body>Not bad.</body></html>'
    submission0.set_points(40, 100)
    submission0.set_ready()
    submission0.save()

    submission1 = Submission.objects.create(id=401, exercise=exercise0)
    submission1.submitters.add(user0.userprofile)
    submission1.submitters.add(user1.userprofile)
    submission1.feedback = '<html><body>Good.</body></html>'
    submission1.set_points(60, 100)
    submission1.set_ready()
    submission1.save()

    submission2 = Submission.objects.create(id=402, exercise=exercise0)
    submission2.submitters.add(user1.userprofile)
    submission2.submitters.add(user4.userprofile)
    submission2.feedback = '<html><body>Good.</body></html>'
    submission2.set_points(50, 100)
    submission2.set_ready()
    submission2.save()

    submission3 = Submission.objects.create(id=403, exercise=exercise2)
    submission3.submitters.add(user0.userprofile)
    submission3.feedback = '<html><body>Excellent.</body></html>'
    submission3.set_points(10, 10)
    submission3.set_ready()
    submission3.save()
コード例 #3
0
def configure_content(instance: CourseInstance,
                      url: str) -> Tuple[bool, List[str]]:
    """
    Configures course content by trusted remote URL.
    """
    if not url:
        return False, [_('COURSE_CONFIG_URL_REQUIRED')]

    # save the url before fetching config. The JWT system requires this to be
    # set, so that A+ knows which service to trust to have access to the course
    # instance. The aplus config url might need access to the course instance.
    # The other service might also need to have access to the course instance
    # before it can be configured from the url.
    instance.configure_url = url
    instance.save()

    try:
        url = url.strip()
        permissions = Permissions()
        permissions.instances.add(Permission.READ, id=instance.id)
        permissions.instances.add(Permission.WRITE, id=instance.id)
        response = aplus_get(url, permissions=permissions)
        response.raise_for_status()
    except Exception as e:
        return False, [
            format_lazy(
                _('COURSE_CONFIG_ERROR_REQUEST_FAILED -- {error!s}'),
                error=e,
            )
        ]

    try:
        config = json.loads(response.text)
    except Exception as e:
        return False, [
            format_lazy(
                _('COURSE_CONFIG_ERROR_JSON_PARSER_FAILED -- {error!s}'),
                error=e,
            )
        ]

    if not isinstance(config, dict):
        return False, [_("COURSE_CONFIG_ERROR_INVALID_JSON")]

    errors = config.get('errors', [])
    if not isinstance(errors, list):
        errors = [str(errors)]

    if not config.get('success', True):
        errors.insert(0, _("COURSE_CONFIG_ERROR_SERVICE_FAILED_TO_EXPORT"))
        return False, errors

    # wrap everything in a transaction to make sure invalid configuration isn't saved
    with transaction.atomic():
        # Configure course instance attributes.
        if "start" in config:
            dt = parse_date(config["start"], errors)
            if dt:
                instance.starting_time = dt
        if "end" in config:
            dt = parse_date(config["end"], errors)
            if dt:
                instance.ending_time = dt
        if "enrollment_start" in config:
            instance.enrollment_starting_time = parse_date(
                config["enrollment_start"], errors, allow_null=True)
        if "enrollment_end" in config:
            instance.enrollment_ending_time = parse_date(
                config["enrollment_end"], errors, allow_null=True)
        if "lifesupport_time" in config:
            instance.lifesupport_time = parse_date(config["lifesupport_time"],
                                                   errors,
                                                   allow_null=True)
        if "archive_time" in config:
            instance.archive_time = parse_date(config["archive_time"],
                                               errors,
                                               allow_null=True)
        if "enrollment_audience" in config:
            enroll_audience = parse_choices(
                config["enrollment_audience"], {
                    'internal':
                    CourseInstance.ENROLLMENT_AUDIENCE.INTERNAL_USERS,
                    'external':
                    CourseInstance.ENROLLMENT_AUDIENCE.EXTERNAL_USERS,
                    'all': CourseInstance.ENROLLMENT_AUDIENCE.ALL_USERS,
                }, "enrollment_audience", errors)
            if enroll_audience is not None:
                instance.enrollment_audience = enroll_audience
        if "view_content_to" in config:
            view_content_to = parse_choices(
                config["view_content_to"], {
                    'enrolled': CourseInstance.VIEW_ACCESS.ENROLLED,
                    'enrollment_audience':
                    CourseInstance.VIEW_ACCESS.ENROLLMENT_AUDIENCE,
                    'all_registered':
                    CourseInstance.VIEW_ACCESS.ALL_REGISTERED,
                    'public': CourseInstance.VIEW_ACCESS.PUBLIC,
                }, "view_content_to", errors)
            if view_content_to is not None:
                instance.view_content_to = view_content_to
        if "index_mode" in config:
            index_mode = parse_choices(
                config["index_mode"], {
                    'results': CourseInstance.INDEX_TYPE.RESULTS,
                    'toc': CourseInstance.INDEX_TYPE.TOC,
                    'last': CourseInstance.INDEX_TYPE.LAST,
                    'experimental': CourseInstance.INDEX_TYPE.EXPERIMENT,
                }, "index_mode", errors)
            if index_mode is not None:
                instance.index_mode = index_mode

        numbering_choices = {
            'none': CourseInstance.CONTENT_NUMBERING.NONE,
            'arabic': CourseInstance.CONTENT_NUMBERING.ARABIC,
            'roman': CourseInstance.CONTENT_NUMBERING.ROMAN,
            'hidden': CourseInstance.CONTENT_NUMBERING.HIDDEN,
        }
        if "content_numbering" in config:
            numbering = parse_choices(config["content_numbering"],
                                      numbering_choices, "content_numbering",
                                      errors)
            if numbering is not None:
                instance.content_numbering = numbering
        if "module_numbering" in config:
            numbering = parse_choices(config["module_numbering"],
                                      numbering_choices, "module_numbering",
                                      errors)
            if numbering is not None:
                instance.module_numbering = numbering
        if "course_description" in config:
            # Course index.yaml files have previously used the field "description"
            # for a hidden description, so we use "course_description" for
            # the visible description.
            instance.description = str(config["course_description"])
        if "course_footer" in config:
            instance.footer = str(config["course_footer"])
        if "lang" in config:
            langs = config["lang"]
            if isinstance(langs, list):
                langs = [
                    lang for lang in langs if instance.is_valid_language(lang)
                ]
                if langs:
                    instance.language = "|{}|".format("|".join(langs))
            elif instance.is_valid_language(langs):
                instance.language = str(langs)[:5]
        if "contact" in config:
            instance.technical_error_emails = str(config["contact"])
        if "head_urls" in config:
            head_urls = config["head_urls"]
            instance.head_urls = "\n".join(head_urls) if isinstance(
                head_urls, list) else str(head_urls)
        if "assistants" in config:
            if not isinstance(config["assistants"], list):
                errors.append(_('COURSE_CONFIG_ERROR_ASSISTANTS_AS_SID_ARRAY'))
            else:
                assistants = []
                for sid in config["assistants"]:
                    try:
                        profile = UserProfile.get_by_student_id(student_id=sid)
                    except UserProfile.DoesNotExist as err:
                        errors.append(
                            format_lazy(
                                _('COURSE_CONFIG_ERROR_ASSISTANT_NO_USER_WITH_SID -- {id}'
                                  ),
                                id=sid,
                            ))
                    else:
                        assistants.append(profile)
                instance.set_assistants(assistants)
        instance.build_log_url = str(
            config['build_log_url']) if 'build_log_url' in config else ''
        # configure_url excluded from validation because the default Django URL
        # validation does not accept dotless domain names such as "grader"
        instance.full_clean(exclude=['configure_url', 'build_log_url'])
        instance.save()

        if not "categories" in config or not isinstance(
                config["categories"], dict):
            errors.append(_('COURSE_CONFIG_ERROR_CATEGORIES_REQUIRED_OBJECT'))
            transaction.set_rollback(True)
            return False, errors
        if not "modules" in config or not isinstance(config["modules"], list):
            errors.append(_('COURSE_CONFIG_ERROR_MODULES_REQUIRED_ARRAY'))
            transaction.set_rollback(True)
            return False, errors

        # Configure learning object categories.
        category_map = {}
        seen = []
        for key, c in config.get("categories", {}).items():
            if not "name" in c:
                errors.append(_('COURSE_CONFIG_ERROR_CATEGORY_REQUIRES_NAME'))
                continue
            try:
                category = instance.categories.get(
                    name=format_localization(c["name"]))
            except LearningObjectCategory.DoesNotExist:
                category = LearningObjectCategory(course_instance=instance,
                                                  name=format_localization(
                                                      c["name"]))
            if "status" in c:
                category.status = str(c["status"])
            if "description" in c:
                category.description = str(c["description"])
            if "points_to_pass" in c:
                i = parse_int(c["points_to_pass"], errors)
                if not i is None:
                    category.points_to_pass = i
            for field in [
                    "confirm_the_level",
                    "accept_unofficial_submits",
            ]:
                if field in c:
                    setattr(category, field, parse_bool(c[field]))
            category.full_clean()
            category.save()
            category_map[key] = category
            seen.append(category.id)

        for category in instance.categories.all():
            if not category.id in seen:
                category.status = LearningObjectCategory.STATUS.HIDDEN
                category.save()

        # Configure course modules.
        seen_modules = []
        seen_objects = []
        nn = 0
        n = 0
        for m in config.get("modules", []):
            if not "key" in m:
                errors.append(_('COURSE_CONFIG_ERROR_MODULE_REQUIRES_KEY'))
                continue
            try:
                module = instance.course_modules.get(url=str(m["key"]))
            except CourseModule.DoesNotExist:
                module = CourseModule(course_instance=instance,
                                      url=str(m["key"]))

            if "order" in m:
                module.order = parse_int(m["order"], errors)
            else:
                n += 1
                module.order = n

            if "title" in m:
                module.name = format_localization(m["title"])
            elif "name" in m:
                module.name = format_localization(m["name"])
            if not module.name:
                module.name = "-"
            if "status" in m:
                module.status = str(m["status"])
            if "points_to_pass" in m:
                i = parse_int(m["points_to_pass"], errors)
                if not i is None:
                    module.points_to_pass = i
            if "introduction" in m:
                module.introduction = str(m["introduction"])

            if "open" in m:
                dt = parse_date(m["open"], errors)
                if dt:
                    module.opening_time = dt
            if not module.opening_time:
                module.opening_time = instance.starting_time

            if "close" in m:
                dt = parse_date(m["close"], errors)
                if dt:
                    module.closing_time = dt
            elif "duration" in m:
                dt = parse_duration(module.opening_time, m["duration"], errors)
                if dt:
                    module.closing_time = dt
            if not module.closing_time:
                module.closing_time = instance.ending_time

            if "read-open" in m:
                module.reading_opening_time = parse_date(m["read-open"],
                                                         errors,
                                                         allow_null=True)

            if "late_close" in m:
                dt = parse_date(m["late_close"], errors)
                if dt:
                    module.late_submission_deadline = dt
                    module.late_submissions_allowed = True
            elif "late_duration" in m:
                dt = parse_duration(module.closing_time, m["late_duration"],
                                    errors)
                if dt:
                    module.late_submission_deadline = dt
                    module.late_submissions_allowed = True
            if "late_penalty" in m:
                f = parse_float(m["late_penalty"], errors)
                if not f is None:
                    module.late_submission_penalty = f

            module.full_clean()
            module.save()
            seen_modules.append(module.id)

            if not ("numerate_ignoring_modules" in config \
                    and parse_bool(config["numerate_ignoring_modules"])):
                nn = 0
            if "children" in m:
                nn = configure_learning_objects(category_map, module,
                                                m["children"], None,
                                                seen_objects, errors, nn)

        for module in instance.course_modules.all():
            # cache invalidation uses the parent when learning object is saved:
            # prefetch parent so that it wont be fetched after the it was deleted
            for lobject in module.learning_objects.all().prefetch_related(
                    'parent'):
                if lobject.id not in seen_objects:
                    exercise = lobject.as_leaf_class()
                    if (not isinstance(exercise, BaseExercise)
                            or exercise.submissions.count() == 0):
                        exercise.delete()
                    else:
                        lobject.status = LearningObject.STATUS.HIDDEN
                        lobject.order = 9999
                        # .parent may have been deleted: only save status and order
                        lobject.save(update_fields=["status", "order"])
            if module.id not in seen_modules:
                if module.learning_objects.count() == 0:
                    module.delete()
                else:
                    module.status = CourseModule.STATUS.HIDDEN
                    module.save()

        # Clean up obsolete categories.
        for category in instance.categories.filter(
                status=LearningObjectCategory.STATUS.HIDDEN):
            if category.learning_objects.count() == 0:
                category.delete()

        if "publish_url" in config:
            success = False
            publish_errors = []
            try:
                permissions = Permissions()
                permissions.instances.add(Permission.READ, id=instance.id)
                permissions.instances.add(Permission.WRITE, id=instance.id)
                response = aplus_get(config["publish_url"],
                                     permissions=permissions)
            except ConnectionError as e:
                publish_errors = [str(e)]
            else:
                if response.status_code != 200:
                    publish_errors = [
                        format_lazy(
                            _("PUBLISH_RESPONSE_NON_200 -- {status_code}"),
                            status_code=response.status_code)
                    ]

                if response.text:
                    try:
                        publish_errors = json.loads(response.text)
                    except Exception as e:
                        publish_errors = [
                            format_lazy(_(
                                "PUBLISH_ERROR_JSON_PARSER_FAILED -- {e}, {text}"
                            ),
                                        e=e,
                                        text=response.text)
                        ]
                    else:
                        if isinstance(publish_errors, dict):
                            success = publish_errors.get("success", True)
                            publish_errors = publish_errors.get("errors", [])

                        if isinstance(publish_errors, list):
                            publish_errors = (str(e) for e in publish_errors)
                        else:
                            publish_errors = [str(publish_errors)]

            if publish_errors:
                if not success:
                    errors.append(
                        format_lazy(
                            _("PUBLISHED_WITH_ERRORS -- {publish_url}"),
                            publish_url=config['publish_url']))
                errors.extend(str(e) for e in publish_errors)

            if not success:
                transaction.set_rollback(True)
                return False, errors

    return True, errors
コード例 #4
0
ファイル: configure.py プロジェクト: Aalto-LeTech/a-plus
def configure_content(instance, url):
    """
    Configures course content by trusted remote URL.
    """
    if not url:
        return [_("Configuration URL required.")]
    try:
        url = url.strip()
        response = requests.get(url)
    except Exception as e:
        return [_("Request for a course configuration failed with error '{error!s}'. "
                  "Configuration of course aborted.").format(error=e)]

    instance.configure_url = url
    instance.save()

    try:
        config = json.loads(response.text)
    except Exception as e:
        return [_("JSON parser raised error '{error!s}'. "
                  "Configuration of course aborted.").format(error=e)]

    errors = []

    # Configure course instance attributes.
    if "start" in config:
        dt = parse_date(config["start"], errors)
        if dt:
            instance.starting_time = dt
    if "end" in config:
        dt = parse_date(config["end"], errors)
        if dt:
            instance.ending_time = dt
    if "enrollment_start" in config:
        dt = parse_date(config["enrollment_start"], errors)
        if dt:
            instance.enrollment_starting_time = dt
    if "enrollment_end" in config:
        dt = parse_date(config["enrollment_end"], errors)
        if dt:
            instance.enrollment_ending_time = dt
    if "lifesupport_time" in config:
        dt = parse_date(config["lifesupport_time"], errors)
        if dt:
            instance.lifesupport_time = dt
    if "archive_time" in config:
        dt = parse_date(config["archive_time"], errors)
        if dt:
            instance.archive_time = dt
    if "enrollment_audience" in config:
        enroll_audience = parse_choices(config["enrollment_audience"], {
                'internal': CourseInstance.ENROLLMENT_AUDIENCE.INTERNAL_USERS,
                'external': CourseInstance.ENROLLMENT_AUDIENCE.EXTERNAL_USERS,
                'all': CourseInstance.ENROLLMENT_AUDIENCE.ALL_USERS,
            }, "enrollment_audience", errors)
        if enroll_audience is not None:
            instance.enrollment_audience = enroll_audience
    if "view_content_to" in config:
        view_content_to = parse_choices(config["view_content_to"], {
                'enrolled': CourseInstance.VIEW_ACCESS.ENROLLED,
                'enrollment_audience': CourseInstance.VIEW_ACCESS.ENROLLMENT_AUDIENCE,
                'all_registered': CourseInstance.VIEW_ACCESS.ALL_REGISTERED,
                'public': CourseInstance.VIEW_ACCESS.PUBLIC,
            }, "view_content_to", errors)
        if view_content_to is not None:
            instance.view_content_to = view_content_to
    if "index_mode" in config:
        index_mode = parse_choices(config["index_mode"], {
                'results': CourseInstance.INDEX_TYPE.RESULTS,
                'toc': CourseInstance.INDEX_TYPE.TOC,
                'last': CourseInstance.INDEX_TYPE.LAST,
                'experimental': CourseInstance.INDEX_TYPE.EXPERIMENT,
            }, "index_mode", errors)
        if index_mode is not None:
            instance.index_mode = index_mode

    numbering_choices = {
        'none': CourseInstance.CONTENT_NUMBERING.NONE,
        'arabic': CourseInstance.CONTENT_NUMBERING.ARABIC,
        'roman': CourseInstance.CONTENT_NUMBERING.ROMAN,
        'hidden': CourseInstance.CONTENT_NUMBERING.HIDDEN,
    }
    if "content_numbering" in config:
        numbering = parse_choices(config["content_numbering"], numbering_choices,
            "content_numbering", errors)
        if numbering is not None:
            instance.content_numbering = numbering
    if "module_numbering" in config:
        numbering = parse_choices(config["module_numbering"], numbering_choices,
            "module_numbering", errors)
        if numbering is not None:
            instance.module_numbering = numbering
    if "course_description" in config:
        # Course index.yaml files have previously used the field "description"
        # for a hidden description, so we use "course_description" for
        # the visible description.
        instance.description = str(config["course_description"])
    if "course_footer" in config:
        instance.footer = str(config["course_footer"])
    if "lang" in config:
        langs = config["lang"]
        if isinstance(langs, list):
            langs = [lang for lang in langs if instance.is_valid_language(lang)]
            if langs:
               instance.language = "|{}|".format("|".join(langs))
        elif instance.is_valid_language(langs):
            instance.language = str(langs)[:5]
    if "contact" in config:
        instance.technical_error_emails = str(config["contact"])
    if "head_urls" in config:
        head_urls = config["head_urls"]
        instance.head_urls = "\n".join(head_urls) if isinstance(head_urls, list) else str(head_urls)
    if "assistants" in config:
        if not isinstance(config["assistants"], list):
            errors.append(_("Assistants must be given as a student ID array."))
        else:
            assistants = []
            for sid in config["assistants"]:
                try:
                    profile = UserProfile.objects.get(student_id=sid)
                except UserProfile.DoesNotExist as err:
                    errors.append(_("Adding the assistant failed, because an associated "
                                    "user with student ID {id} does not exist.").format(id=sid))
                else:
                    assistants.append(profile)
            instance.assistants.set(assistants)
    if "build_log_url" in config:
        instance.build_log_url = str(config["build_log_url"])
    # configure_url excluded from validation because the default Django URL
    # validation does not accept dotless domain names such as "grader"
    instance.full_clean(exclude=['configure_url'])
    instance.save()

    if not "categories" in config or not isinstance(config["categories"], dict):
        errors.append(_("Categories required as an object."))
        return errors
    if not "modules" in config or not isinstance(config["modules"], list):
        errors.append(_("Modules required as an array."))
        return errors

    # Configure learning object categories.
    category_map = {}
    seen = []
    for key, c in config.get("categories", {}).items():
        if not "name" in c:
            errors.append(_("Category requires a name."))
            continue
        try:
            category = instance.categories.get(name=format_localization(c["name"]))
        except LearningObjectCategory.DoesNotExist:
            category = LearningObjectCategory(course_instance=instance,
                name=format_localization(c["name"]))
        if "status" in c:
            category.status = str(c["status"])[:32]
        if "description" in c:
            category.description = str(c["description"])
        if "points_to_pass" in c:
            i = parse_int(c["points_to_pass"], errors)
            if not i is None:
                category.points_to_pass = i
        for field in [
            "confirm_the_level",
            "accept_unofficial_submits",
        ]:
            if field in c:
                setattr(category, field, parse_bool(c[field]))
        category.full_clean()
        category.save()
        category_map[key] = category
        seen.append(category.id)

    for category in instance.categories.all():
        if not category.id in seen:
            category.status = 'hidden'
            category.save()

    # Configure course modules.
    seen_modules = []
    seen_objects = []
    nn = 0
    n = 0
    for m in config.get("modules", []):
        if not "key" in m:
            errors.append(_("Module requires a key."))
            continue
        try:
            module = instance.course_modules.get(url=str(m["key"]))
        except CourseModule.DoesNotExist:
            module = CourseModule(course_instance=instance, url=str(m["key"]))

        if "order" in m:
            module.order = parse_int(m["order"], errors)
        else:
            n += 1
            module.order = n

        if "title" in m:
            module.name = format_localization(m["title"])
        elif "name" in m:
            module.name = format_localization(m["name"])
        if not module.name:
            module.name = "-"
        if "status" in m:
            module.status = str(m["status"])[:32]
        if "points_to_pass" in m:
            i = parse_int(m["points_to_pass"], errors)
            if not i is None:
                module.points_to_pass = i
        if "introduction" in m:
            module.introduction = str(m["introduction"])

        if "open" in m:
            dt = parse_date(m["open"], errors)
            if dt:
                module.opening_time = dt
        if not module.opening_time:
            module.opening_time = instance.starting_time

        if "close" in m:
            dt = parse_date(m["close"], errors)
            if dt:
                module.closing_time = dt
        elif "duration" in m:
            dt = parse_duration(module.opening_time, m["duration"], errors)
            if dt:
                module.closing_time = dt
        if not module.closing_time:
            module.closing_time = instance.ending_time

        if "late_close" in m:
            dt = parse_date(m["late_close"], errors)
            if dt:
                module.late_submission_deadline = dt
                module.late_submissions_allowed = True
        elif "late_duration" in m:
            dt = parse_duration(module.closing_time, m["late_duration"], errors)
            if dt:
                module.late_submission_deadline = dt
                module.late_submissions_allowed = True
        if "late_penalty" in m:
            f = parse_float(m["late_penalty"], errors)
            if not f is None:
                module.late_submission_penalty = f

        module.full_clean()
        module.save()
        seen_modules.append(module.id)

        if not ("numerate_ignoring_modules" in config \
                and parse_bool(config["numerate_ignoring_modules"])):
            nn = 0
        if "children" in m:
            nn = configure_learning_objects(category_map, module, m["children"],
                None, seen_objects, errors, nn)

    for module in list(instance.course_modules.all()):
        if not module.id in seen_modules:
            module.status = "hidden"
            module.save()
        for lobject in list(module.learning_objects.all()):
            if not lobject.id in seen_objects:
                exercise = lobject.as_leaf_class()
                if (
                    not isinstance(exercise, BaseExercise)
                    or exercise.submissions.count() == 0
                ):
                    exercise.delete()
                else:
                    lobject.status = "hidden"
                    lobject.order = 9999
                    lobject.save()

    # Clean up obsolete categories.
    for category in instance.categories.filter(status="hidden"):
        if category.learning_objects.count() == 0:
            category.delete()

    return errors