def test_compatible_version(self):
     addon = addon_factory()
     version = version_factory(addon=addon, version='99')
     version_factory(addon=addon,
                     version='100',
                     channel=amo.RELEASE_CHANNEL_UNLISTED)
     assert find_compatible_version(addon, amo.FIREFOX.id) == version
Esempio n. 2
0
 def test_compatible_version(self):
     addon = addon_factory()
     version = version_factory(addon=addon, version='99')
     version_factory(
         addon=addon, version='100',
         channel=amo.RELEASE_CHANNEL_UNLISTED)
     assert find_compatible_version(addon, amo.FIREFOX.id) == version
Esempio n. 3
0
    def process_request(self, query, addon_type='ALL', limit=10,
                        platform='ALL', version=None, compat_mode='strict'):
        """
        Query the search backend and serve up the XML.
        """
        limit = min(MAX_LIMIT, int(limit))
        app_id = self.request.APP.id

        # We currently filter for status=PUBLIC for all versions. If
        # that changes, the contract for API version 1.5 requires
        # that we continue filtering for it there.
        filters = {
            'app': app_id,
            'status': amo.STATUS_PUBLIC,
            'is_experimental': False,
            'is_disabled': False,
            'current_version__exists': True,
        }

        # Opts may get overridden by query string filters.
        opts = {
            'addon_type': addon_type,
            'version': version,
        }
        # Specific case for Personas (bug 990768): if we search providing the
        # Persona addon type (9), don't filter on the platform as Personas
        # don't have compatible platforms to filter on.
        if addon_type != '9':
            opts['platform'] = platform

        if self.version < 1.5:
            # Fix doubly encoded query strings.
            try:
                query = urllib.unquote(query.encode('ascii'))
            except UnicodeEncodeError:
                # This fails if the string is already UTF-8.
                pass

        query, qs_filters, params = extract_filters(query, opts)

        qs = Addon.search().filter_query_string(query)

        filters.update(qs_filters)
        if 'type' not in filters:
            # Filter by ALL types, which is really all types except for apps.
            filters['type__in'] = list(amo.ADDON_SEARCH_TYPES)
        qs = qs.filter(**filters)

        qs = qs[:limit]
        total = qs.count()

        results = []
        for addon in qs:
            compat_version = find_compatible_version(
                addon, app_id, params['version'], params['platform'],
                compat_mode)
            # Specific case for Personas (bug 990768): if we search providing
            # the Persona addon type (9), then don't look for a compatible
            # version.
            if compat_version or addon_type == '9':
                addon.compat_version = compat_version
                results.append(addon)
                if len(results) == limit:
                    break
            else:
                # We're excluding this addon because there are no
                # compatible versions. Decrement the total.
                total -= 1

        return self.render('legacy_api/search.xml', {
            'results': results,
            'total': total,
            # For caching
            'version': version,
            'compat_mode': compat_mode,
        })
Esempio n. 4
0
def addon_filter(addons, addon_type, limit, app, platform, version,
                 compat_mode='strict', shuffle=True):
    """
    Filter addons by type, application, app version, and platform.

    Add-ons that support the current locale will be sorted to front of list.
    Shuffling will be applied to the add-ons supporting the locale and the
    others separately.

    Doing this in the database takes too long, so we in code and wrap it in
    generous caching.
    """
    APP = app

    if addon_type.upper() != 'ALL':
        try:
            addon_type = int(addon_type)
            if addon_type:
                addons = [a for a in addons if a.type == addon_type]
        except ValueError:
            # `addon_type` is ALL or a type id.  Otherwise we ignore it.
            pass

    # Take out personas since they don't have versions.
    groups = dict(partition(addons,
                            lambda x: x.type == amo.ADDON_PERSONA))
    personas, addons = groups.get(True, []), groups.get(False, [])

    platform = platform.lower()
    if platform != 'all' and platform in amo.PLATFORM_DICT:
        def f(ps):
            return pid in ps or amo.PLATFORM_ALL in ps

        pid = amo.PLATFORM_DICT[platform]
        addons = [a for a in addons
                  if f(a.current_version.supported_platforms)]

    if version is not None:
        vint = version_int(version)

        def f_strict(app):
            return app.min.version_int <= vint <= app.max.version_int

        def f_ignore(app):
            return app.min.version_int <= vint

        xs = [(a, a.compatible_apps) for a in addons]

        # Iterate over addons, checking compatibility depending on compat_mode.
        addons = []
        for addon, apps in xs:
            app = apps.get(APP)
            if compat_mode == 'strict':
                if app and f_strict(app):
                    addons.append(addon)
            elif compat_mode == 'ignore':
                if app and f_ignore(app):
                    addons.append(addon)
            elif compat_mode == 'normal':
                # This does a db hit but it's cached. This handles the cases
                # for strict opt-in, binary components, and compat overrides.
                v = find_compatible_version(addon, APP.id, version, platform,
                                            compat_mode)
                if v:  # There's a compatible version.
                    addons.append(addon)

    # Put personas back in.
    addons.extend(personas)

    # We prefer add-ons that support the current locale.
    lang = get_language()

    def partitioner(x):
        return x.description is not None and (x.description.locale == lang)

    groups = dict(partition(addons, partitioner))
    good, others = groups.get(True, []), groups.get(False, [])

    if shuffle:
        random.shuffle(good)
        random.shuffle(others)

    # If limit=0, we return all addons with `good` coming before `others`.
    # Otherwise pad `good` if less than the limit and return the limit.
    if limit > 0:
        if len(good) < limit:
            good.extend(others[:limit - len(good)])
        return good[:limit]
    else:
        good.extend(others)
        return good
Esempio n. 5
0
    def process_request(self,
                        query,
                        addon_type='ALL',
                        limit=10,
                        platform='ALL',
                        version=None,
                        compat_mode='strict'):
        """
        Query the search backend and serve up the XML.
        """
        limit = min(MAX_LIMIT, int(limit))
        app_id = self.request.APP.id

        # We currently filter for status=PUBLIC for all versions. If
        # that changes, the contract for API version 1.5 requires
        # that we continue filtering for it there.
        filters = {
            'app': app_id,
            'status': amo.STATUS_PUBLIC,
            'is_experimental': False,
            'is_disabled': False,
            'current_version__exists': True,
        }

        params = {'version': version, 'platform': None}

        # Specific case for Personas (bug 990768): if we search providing the
        # Persona addon type (9), don't filter on the platform as Personas
        # don't have compatible platforms to filter on.
        if addon_type != '9':
            params['platform'] = platform

        # Type filters.
        if addon_type:
            try:
                atype = int(addon_type)
                if atype in amo.ADDON_SEARCH_TYPES:
                    filters['type'] = atype
            except ValueError:
                atype = amo.ADDON_SEARCH_SLUGS.get(addon_type.lower())
                if atype:
                    filters['type'] = atype

        if 'type' not in filters:
            # Filter by ALL types, which is really all types except for apps.
            filters['type__in'] = list(amo.ADDON_SEARCH_TYPES)

        if self.version < 1.5:
            # Fix doubly encoded query strings.
            try:
                query = urllib.unquote(query.encode('ascii'))
            except UnicodeEncodeError:
                # This fails if the string is already UTF-8.
                pass

        qs = (Addon.search().filter(
            **filters).filter_query_string(query)[:limit])

        results = []

        total = qs.count()

        for addon in qs:
            compat_version = find_compatible_version(addon, app_id,
                                                     params['version'],
                                                     params['platform'],
                                                     compat_mode)
            # Specific case for Personas (bug 990768): if we search
            # providing the Persona addon type (9), then don't look for a
            # compatible version.
            if compat_version or addon_type == '9':
                addon.compat_version = compat_version
                results.append(addon)
                if len(results) == limit:
                    break
            else:
                # We're excluding this addon because there are no
                # compatible versions. Decrement the total.
                total -= 1

        return self.render(
            'legacy_api/search.xml',
            {
                'results': results,
                'total': total,
                # For caching
                'version': version,
                'compat_mode': compat_mode,
            })
Esempio n. 6
0
def addon_filter(addons,
                 addon_type,
                 limit,
                 app,
                 platform,
                 version,
                 compat_mode='strict',
                 shuffle=True):
    """
    Filter addons by type, application, app version, and platform.

    Add-ons that support the current locale will be sorted to front of list.
    Shuffling will be applied to the add-ons supporting the locale and the
    others separately.

    Doing this in the database takes too long, so we do it in code and wrap
    it in generous caching.
    """
    APP = app

    if addon_type.upper() != 'ALL':
        try:
            addon_type = int(addon_type)
            if addon_type:
                addons = [a for a in addons if a.type == addon_type]
        except ValueError:
            # `addon_type` is ALL or a type id.  Otherwise we ignore it.
            pass

    # Take out personas since they don't have versions.
    groups = dict(partition(addons, lambda x: x.type == amo.ADDON_PERSONA))
    personas, addons = groups.get(True, []), groups.get(False, [])

    platform = platform.lower()
    if platform != 'all' and platform in amo.PLATFORM_DICT:

        def f(ps):
            return pid in ps or amo.PLATFORM_ALL in ps

        pid = amo.PLATFORM_DICT[platform]
        addons = [
            a for a in addons if f(a.current_version.supported_platforms)
        ]

    if version is not None:
        vint = version_int(version)

        def f_strict(app):
            return app.min.version_int <= vint <= app.max.version_int

        def f_ignore(app):
            return app.min.version_int <= vint

        xs = [(a, a.compatible_apps) for a in addons]

        # Iterate over addons, checking compatibility depending on compat_mode.
        addons = []
        for addon, apps in xs:
            app = apps.get(APP)
            if compat_mode == 'strict':
                if app and f_strict(app):
                    addons.append(addon)
            elif compat_mode == 'ignore':
                if app and f_ignore(app):
                    addons.append(addon)
            elif compat_mode == 'normal':
                # This does a db hit but it's cached. This handles the cases
                # for strict opt-in, binary components, and compat overrides.
                v = find_compatible_version(addon, APP.id, version, platform,
                                            compat_mode)
                if v:  # There's a compatible version.
                    addons.append(addon)

    # Put personas back in.
    addons.extend(personas)

    # We prefer add-ons that support the current locale.
    lang = get_language()

    def partitioner(x):
        return x.description is not None and (x.description.locale == lang)

    groups = dict(partition(addons, partitioner))
    good, others = groups.get(True, []), groups.get(False, [])

    if shuffle:
        random.shuffle(good)
        random.shuffle(others)

    # If limit=0, we return all addons with `good` coming before `others`.
    # Otherwise pad `good` if less than the limit and return the limit.
    if limit > 0:
        if len(good) < limit:
            good.extend(others[:limit - len(good)])
        return good[:limit]
    else:
        good.extend(others)
        return good