Example #1
0
    def current(cls, entity_id):
        """
        Return the active data entry, if any, otherwise None
        """
        cached = cache.get(cls.cache_key_name(entity_id))
        if cached is not None:
            return cached

        try:
            current = cls.objects.filter(entity_id=entity_id).order_by('-fetched_at')[0]
        except IndexError:
            current = None

        cache.set(cls.cache_key_name(entity_id), current, cls.cache_timeout)
        return current
Example #2
0
    def current(cls, site=None, org=None, course_key=None):  # pylint: disable=arguments-differ
        """
        Return the current overridden configuration at the specified level.

        Only one level may be specified at a time. Specifying multiple levels
        will result in a ValueError.

        For example, considering the following set of requirements:

            Global: Feature Disabled
            edx.org (Site): Feature Enabled
            Harvard (org): Feature Disabled
            CS50 (course): Feature Enabled

        Assuming these values had been properly configured, these calls would result

            MyStackedConfig.current()  # False
            MyStackedConfig.current(site=Site(domain='edx.org'))  # True
            MyStackedConfig.current(site=Site(domain='whitelabel.edx.org')) # False -- value derived from global setting
            MyStackedConfig.current(org='HarvardX')  # False
            MyStackedConfig.current(org='MITx')  # True -- value derived from edx.org site
            MyStackedConfig.current(course=CourseKey(org='HarvardX', course='CS50', run='2018_Q1'))  # True

            cs50 = CourseKey(org='HarvardX', course='Bio101', run='2018_Q1')
            MyStackedConfig.current(course=cs50)  # False -- value derived from HarvardX org

        The following calls would result in errors due to overspecification:

            MyStackedConfig.current(site=Site(domain='edx.org'), org='HarvardX')
            MyStackedConfig.current(site=Site(domain='edx.org'), course=cs50)
            MyStackedConfig.current(org='HarvardX', course=cs50)

        Arguments:
            site: The Site to check current values for
            org: The org to check current values for
            course: The course to check current values for

        Returns:
            An instance of :class:`cls.attribute_tuple()` with the overridden values
            specified down to the level of the supplied argument (or global values if
            no arguments are supplied).
        """
        cache_key_name = cls.cache_key_name(site, org, course_key=course_key)
        cached = cache.get(cache_key_name)

        if cached is not None:
            return cached

        # Raise an error if more than one of site/org/course are specified simultaneously.
        if len([arg for arg in [site, org, course_key] if arg is not None]) > 1:
            raise ValueError("Only one of site, org, and course can be specified")

        if org is None and course_key is not None:
            org = cls._org_from_course_key(course_key)

        if site is None and org is not None:
            site = cls._site_from_org(org)

        stackable_fields = [cls._meta.get_field(field_name) for field_name in cls.STACKABLE_FIELDS]
        field_defaults = {
            field.name: field.get_default()
            for field in stackable_fields
        }

        values = field_defaults.copy()

        global_override_q = Q(site=None, org=None, course_id=None)
        site_override_q = Q(site=site, org=None, course_id=None)
        org_override_q = Q(site=None, org=org, course_id=None)
        course_override_q = Q(site=None, org=None, course_id=course_key)

        overrides = cls.objects.current_set().filter(
            global_override_q |
            site_override_q |
            org_override_q |
            course_override_q
        ).order_by(
            # Sort nulls first, and in reverse specificity order
            # so that the overrides are in the order of general to specific.
            #
            # Site | Org  | Course
            # --------------------
            # Null | Null | Null
            # site | Null | Null
            # Null | org  | Null
            # Null | Null | Course
            F('course').desc(nulls_first=True),
            F('org').desc(nulls_first=True),
            F('site').desc(nulls_first=True),
        )

        for override in overrides:
            for field in stackable_fields:
                value = field.value_from_object(override)
                if value != field_defaults[field.name]:
                    values[field.name] = value

        current = cls(**values)
        cache.set(cache_key_name, current, cls.cache_timeout)
        return current
Example #3
0
    def current(cls, site=None, org=None, org_course=None, course_key=None):  # pylint: disable=arguments-differ
        """
        Return the current overridden configuration at the specified level.

        Only one level may be specified at a time. Specifying multiple levels
        will result in a ValueError.

        For example, considering the following set of requirements:

            Global: Feature Disabled
            edx.org (Site): Feature Enabled
            HarvardX (org): Feature Disabled
            HarvardX/CS50 (org_course): Feature Enabled
            CS50 in 2018_Q1 (course run): Feature Disabled

        Assuming these values had been properly configured, these calls would result

            MyStackedConfig.current()  # False
            MyStackedConfig.current(site=Site(domain='edx.org'))  # True
            MyStackedConfig.current(site=Site(domain='whitelabel.edx.org')) # False -- value derived from global setting
            MyStackedConfig.current(org='HarvardX')  # False
            MyStackedConfig.current(org='MITx')  # True -- value derived from edx.org site
            MyStackedConfig.current(org_course='HarvardX/CS50')  # True
            MyStackedConfig.current(org_course='HarvardX/Bio101')  # False -- value derived from HarvardX setting
            MyStackedConfig.current(course_key=CourseKey(org='HarvardX', course='CS50', run='2018_Q1'))  # False
            MyStackedConfig.current(
                course_key=CourseKey(org='HarvardX', course='CS50', run='2019_Q1')
            )  # True -- value derived from HarvardX/CS50 setting

            bio101 = CourseKey(org='HarvardX', course='Bio101', run='2018_Q1')
            MyStackedConfig.current(course_key=cs50)  # False -- value derived from HarvardX org

        The following calls would result in errors due to overspecification:

            MyStackedConfig.current(site=Site(domain='edx.org'), org='HarvardX')
            MyStackedConfig.current(site=Site(domain='edx.org'), course=cs50)
            MyStackedConfig.current(org='HarvardX', course=cs50)

        Arguments:
            site: The Site to check current values for
            org: The org to check current values for
            org_course: The course in a specific org to check current values for
            course_key: The course to check current values for

        Returns:
            An instance of :class:`cls.attribute_tuple()` with the overridden values
            specified down to the level of the supplied argument (or global values if
            no arguments are supplied).
        """
        cache_key_name = cls.cache_key_name(site, org, org_course, course_key)
        cached = cache.get(cache_key_name)

        if cached is not None:
            return cached

        # Raise an error if more than one of site/org/course are specified simultaneously.
        if len([
                arg for arg in [site, org, org_course, course_key]
                if arg is not None
        ]) > 1:
            raise ValueError(
                "Only one of site, org, org_course, and course can be specified"
            )

        if org_course is None and course_key is not None:
            org_course = cls._org_course_from_course_key(course_key)

        if org is None and org_course is not None:
            org = cls._org_from_org_course(org_course)

        if site is None and org is not None:
            site = cls._site_from_org(org)

        stackable_fields = [
            cls._meta.get_field(field_name)
            for field_name in cls.STACKABLE_FIELDS
        ]
        field_defaults = {
            field.name: field.get_default()
            for field in stackable_fields
        }

        values = field_defaults.copy()

        global_override_q = Q(site=None,
                              org=None,
                              org_course=None,
                              course_id=None)
        site_override_q = Q(site=site,
                            org=None,
                            org_course=None,
                            course_id=None)
        org_override_q = Q(site=None, org=org, org_course=None, course_id=None)
        org_course_override_q = Q(site=None,
                                  org=None,
                                  org_course=org_course,
                                  course_id=None)
        course_override_q = Q(site=None,
                              org=None,
                              org_course=None,
                              course_id=course_key)

        overrides = cls.objects.current_set().filter(global_override_q
                                                     | site_override_q
                                                     | org_override_q
                                                     | org_course_override_q
                                                     | course_override_q)

        provenances = defaultdict(lambda: Provenance.default)

        # We are sorting in python to avoid doing a filesort in the database for
        # what will only be 4 rows at maximum

        def sort_key(override):
            """
            Sort overrides in increasing specificity.

            This particular sort order sorts None before not-None (because False < True)
            It sorts global first (because all entries are None), then site entries
            (because course_id and org are None), then org, org_course and course (by the same logic)
            """
            return (override.course_id is not None, override.org_course
                    is not None, override.org is not None, override.site_id
                    is not None)

        for override in sorted(overrides, key=sort_key):
            for field in stackable_fields:
                value = field.value_from_object(override)
                if value != field_defaults[field.name]:
                    values[field.name] = value
                    if override.course_id is not None:
                        provenances[field.name] = Provenance.run
                    elif override.org_course is not None:
                        provenances[field.name] = Provenance.org_course
                    elif override.org is not None:
                        provenances[field.name] = Provenance.org
                    elif override.site_id is not None:
                        provenances[field.name] = Provenance.site
                    else:
                        provenances[field.name] = Provenance.global_

        current = cls(**values)
        current.provenances = {
            field.name: provenances[field.name]
            for field in stackable_fields
        }  # pylint: disable=attribute-defined-outside-init
        cache.set(cache_key_name, current, cls.cache_timeout)
        return current
Example #4
0
    def current(cls, site=None, org=None, course_key=None):  # pylint: disable=arguments-differ
        """
        Return the current overridden configuration at the specified level.

        Only one level may be specified at a time. Specifying multiple levels
        will result in a ValueError.

        For example, considering the following set of requirements:

            Global: Feature Disabled
            edx.org (Site): Feature Enabled
            Harvard (org): Feature Disabled
            CS50 (course): Feature Enabled

        Assuming these values had been properly configured, these calls would result

            MyStackedConfig.current()  # False
            MyStackedConfig.current(site=Site(domain='edx.org'))  # True
            MyStackedConfig.current(site=Site(domain='whitelabel.edx.org')) # False -- value derived from global setting
            MyStackedConfig.current(org='HarvardX')  # False
            MyStackedConfig.current(org='MITx')  # True -- value derived from edx.org site
            MyStackedConfig.current(course=CourseKey(org='HarvardX', course='CS50', run='2018_Q1'))  # True

            cs50 = CourseKey(org='HarvardX', course='Bio101', run='2018_Q1')
            MyStackedConfig.current(course=cs50)  # False -- value derived from HarvardX org

        The following calls would result in errors due to overspecification:

            MyStackedConfig.current(site=Site(domain='edx.org'), org='HarvardX')
            MyStackedConfig.current(site=Site(domain='edx.org'), course=cs50)
            MyStackedConfig.current(org='HarvardX', course=cs50)

        Arguments:
            site: The Site to check current values for
            org: The org to check current values for
            course: The course to check current values for

        Returns:
            An instance of :class:`cls.attribute_tuple()` with the overridden values
            specified down to the level of the supplied argument (or global values if
            no arguments are supplied).
        """
        cache_key_name = cls.cache_key_name(site, org, course_key=course_key)
        cached = cache.get(cache_key_name)

        if cached is not None:
            return cached

        # Raise an error if more than one of site/org/course are specified simultaneously.
        if len([arg
                for arg in [site, org, course_key] if arg is not None]) > 1:
            raise ValueError(
                "Only one of site, org, and course can be specified")

        if org is None and course_key is not None:
            org = cls._org_from_course_key(course_key)

        if site is None and org is not None:
            site = cls._site_from_org(org)

        stackable_fields = [
            cls._meta.get_field(field_name)
            for field_name in cls.STACKABLE_FIELDS
        ]
        field_defaults = {
            field.name: field.get_default()
            for field in stackable_fields
        }

        values = field_defaults.copy()

        global_override_q = Q(site=None, org=None, course_id=None)
        site_override_q = Q(site=site, org=None, course_id=None)
        org_override_q = Q(site=None, org=org, course_id=None)
        course_override_q = Q(site=None, org=None, course_id=course_key)

        overrides = cls.objects.current_set().filter(
            global_override_q | site_override_q | org_override_q
            | course_override_q
        ).order_by(
            # Sort nulls first, and in reverse specificity order
            # so that the overrides are in the order of general to specific.
            #
            # Site | Org  | Course
            # --------------------
            # Null | Null | Null
            # site | Null | Null
            # Null | org  | Null
            # Null | Null | Course
            F('course').desc(nulls_first=True),
            F('org').desc(nulls_first=True),
            F('site').desc(nulls_first=True),
        )

        for override in overrides:
            for field in stackable_fields:
                value = field.value_from_object(override)
                if value != field_defaults[field.name]:
                    values[field.name] = value

        current = cls(**values)
        cache.set(cache_key_name, current, cls.cache_timeout)
        return current
Example #5
0
    def current(cls, site=None, org=None, org_course=None, course_key=None):  # pylint: disable=arguments-differ
        """
        Return the current overridden configuration at the specified level.

        Only one level may be specified at a time. Specifying multiple levels
        will result in a ValueError.

        For example, considering the following set of requirements:

            Global: Feature Disabled
            edx.org (Site): Feature Enabled
            HarvardX (org): Feature Disabled
            HarvardX/CS50 (org_course): Feature Enabled
            CS50 in 2018_Q1 (course run): Feature Disabled

        Assuming these values had been properly configured, these calls would result

            MyStackedConfig.current()  # False
            MyStackedConfig.current(site=Site(domain='edx.org'))  # True
            MyStackedConfig.current(site=Site(domain='whitelabel.edx.org')) # False -- value derived from global setting
            MyStackedConfig.current(org='HarvardX')  # False
            MyStackedConfig.current(org='MITx')  # True -- value derived from edx.org site
            MyStackedConfig.current(org_course='HarvardX/CS50')  # True
            MyStackedConfig.current(org_course='HarvardX/Bio101')  # False -- value derived from HarvardX setting
            MyStackedConfig.current(course_key=CourseKey(org='HarvardX', course='CS50', run='2018_Q1'))  # False
            MyStackedConfig.current(
                course_key=CourseKey(org='HarvardX', course='CS50', run='2019_Q1')
            )  # True -- value derived from HarvardX/CS50 setting

            bio101 = CourseKey(org='HarvardX', course='Bio101', run='2018_Q1')
            MyStackedConfig.current(course_key=cs50)  # False -- value derived from HarvardX org

        The following calls would result in errors due to overspecification:

            MyStackedConfig.current(site=Site(domain='edx.org'), org='HarvardX')
            MyStackedConfig.current(site=Site(domain='edx.org'), course=cs50)
            MyStackedConfig.current(org='HarvardX', course=cs50)

        Arguments:
            site: The Site to check current values for
            org: The org to check current values for
            org_course: The course in a specific org to check current values for
            course_key: The course to check current values for

        Returns:
            An instance of :class:`cls.attribute_tuple()` with the overridden values
            specified down to the level of the supplied argument (or global values if
            no arguments are supplied).
        """
        cache_key_name = cls.cache_key_name(site, org, org_course, course_key)
        cached = cache.get(cache_key_name)

        if cached is not None:
            return cached

        # Raise an error if more than one of site/org/course are specified simultaneously.
        if len([arg for arg in [site, org, org_course, course_key] if arg is not None]) > 1:
            raise ValueError("Only one of site, org, org_course, and course can be specified")

        if org_course is None and course_key is not None:
            org_course = cls._org_course_from_course_key(course_key)

        if org is None and org_course is not None:
            org = cls._org_from_org_course(org_course)

        if site is None and org is not None:
            site = cls._site_from_org(org)

        stackable_fields = [cls._meta.get_field(field_name) for field_name in cls.STACKABLE_FIELDS]
        field_defaults = {
            field.name: field.get_default()
            for field in stackable_fields
        }

        values = field_defaults.copy()

        global_override_q = Q(site=None, org=None, org_course=None, course_id=None)
        site_override_q = Q(site=site, org=None, org_course=None, course_id=None)
        org_override_q = Q(site=None, org=org, org_course=None, course_id=None)
        org_course_override_q = Q(site=None, org=None, org_course=org_course, course_id=None)
        course_override_q = Q(site=None, org=None, org_course=None, course_id=course_key)

        overrides = cls.objects.current_set().filter(
            global_override_q |
            site_override_q |
            org_override_q |
            org_course_override_q |
            course_override_q
        )

        provenances = defaultdict(lambda: Provenance.default)
        # We are sorting in python to avoid doing a filesort in the database for
        # what will only be 4 rows at maximum

        def sort_key(override):
            """
            Sort overrides in increasing specificity.

            This particular sort order sorts None before not-None (because False < True)
            It sorts global first (because all entries are None), then site entries
            (because course_id and org are None), then org, org_course and course (by the same logic)
            """
            return (
                override.course_id is not None,
                override.org_course is not None,
                override.org is not None,
                override.site_id is not None
            )

        for override in sorted(overrides, key=sort_key):
            for field in stackable_fields:
                value = field.value_from_object(override)
                if value != field_defaults[field.name]:
                    values[field.name] = value
                    if override.course_id is not None:
                        provenances[field.name] = Provenance.run
                    elif override.org_course is not None:
                        provenances[field.name] = Provenance.org_course
                    elif override.org is not None:
                        provenances[field.name] = Provenance.org
                    elif override.site_id is not None:
                        provenances[field.name] = Provenance.site
                    else:
                        provenances[field.name] = Provenance.global_

        current = cls(**values)
        current.provenances = {field.name: provenances[field.name] for field in stackable_fields}  # pylint: disable=attribute-defined-outside-init
        cache.set(cache_key_name, current, cls.cache_timeout)
        return current