def test_signature_parity(self): # Test flags -> signature -> flags works as expected. self._flag() signature = self.app.current_version.features.to_signature() eq_(signature.count('.'), 2, 'Unexpected signature format') af = AppFeatures(version=self.app.current_version) af.set_flags(signature) self._check(af)
def get_changed_features(self): old_features = dict.fromkeys(self.initial_feature_keys, True) old_features = set(AppFeatures(**old_features).to_names()) new_features = set(self.instance.to_names()) added_features = new_features - old_features removed_features = old_features - new_features return added_features, removed_features
def _changed_features(self): old_features = defaultdict.fromkeys(self.initial_features, True) old_features = set( unicode(f) for f in AppFeatures(**old_features).to_list()) new_features = set(unicode(f) for f in self.instance.to_list()) added_features = new_features - old_features removed_features = old_features - new_features return added_features, removed_features
def extract_document(cls, pk=None, obj=None): """Extracts the ElasticSearch index document for this instance.""" from mkt.webapps.models import (AppFeatures, attach_devices, attach_prices, attach_tags, attach_translations, Geodata, Installed, RatingDescriptors, RatingInteractives) if obj is None: obj = cls.get_model().objects.no_cache().get(pk=pk) # Attach everything we need to index apps. for transform in (attach_devices, attach_prices, attach_tags, attach_translations): transform([obj]) latest_version = obj.latest_version version = obj.current_version geodata = obj.geodata features = (version.features.to_dict() if version else AppFeatures().to_dict()) try: status = latest_version.statuses[0][1] if latest_version else None except IndexError: status = None installed_ids = list(Installed.objects.filter(addon=obj) .values_list('id', flat=True)) attrs = ('app_slug', 'bayesian_rating', 'created', 'id', 'is_disabled', 'last_updated', 'modified', 'premium_type', 'status', 'uses_flash', 'weekly_downloads') d = dict(zip(attrs, attrgetter(*attrs)(obj))) d['boost'] = len(installed_ids) or 1 d['app_type'] = obj.app_type_id d['author'] = obj.developer_name d['banner_regions'] = geodata.banner_regions_slugs() d['category'] = obj.categories if obj.categories else [] if obj.is_published: d['collection'] = [{'id': cms.collection_id, 'order': cms.order} for cms in obj.collectionmembership_set.all()] else: d['collection'] = [] d['content_ratings'] = (obj.get_content_ratings_by_body(es=True) or None) try: d['content_descriptors'] = obj.rating_descriptors.to_keys() except RatingDescriptors.DoesNotExist: d['content_descriptors'] = [] d['current_version'] = version.version if version else None d['default_locale'] = obj.default_locale d['description'] = list( set(string for _, string in obj.translations[obj.description_id])) d['device'] = getattr(obj, 'device_ids', []) d['features'] = features d['has_public_stats'] = obj.public_stats d['icon_hash'] = obj.icon_hash try: d['interactive_elements'] = obj.rating_interactives.to_keys() except RatingInteractives.DoesNotExist: d['interactive_elements'] = [] d['is_escalated'] = obj.escalationqueue_set.exists() d['is_offline'] = getattr(obj, 'is_offline', False) d['is_priority'] = obj.priority_review d['is_rereviewed'] = obj.rereviewqueue_set.exists() if latest_version: d['latest_version'] = { 'status': status, 'is_privileged': latest_version.is_privileged, 'has_editor_comment': latest_version.has_editor_comment, 'has_info_request': latest_version.has_info_request, 'nomination_date': latest_version.nomination, 'created_date': latest_version.created, } else: d['latest_version'] = { 'status': None, 'is_privileged': None, 'has_editor_comment': None, 'has_info_request': None, 'nomination_date': None, 'created_date': None, } d['manifest_url'] = obj.get_manifest_url() d['package_path'] = obj.get_package_path() d['name'] = list( set(string for _, string in obj.translations[obj.name_id])) d['name_sort'] = unicode(obj.name).lower() d['owners'] = [au.user.id for au in obj.addonuser_set.filter(role=amo.AUTHOR_ROLE_OWNER)] d['popularity'] = len(installed_ids) d['previews'] = [{'filetype': p.filetype, 'modified': p.modified, 'id': p.id, 'sizes': p.sizes} for p in obj.previews.all()] try: p = obj.addonpremium.price d['price_tier'] = p.name except AddonPremium.DoesNotExist: d['price_tier'] = None d['ratings'] = { 'average': obj.average_rating, 'count': obj.total_reviews, } d['region_exclusions'] = obj.get_excluded_region_ids() d['reviewed'] = obj.versions.filter( deleted=False).aggregate(Min('reviewed')).get('reviewed__min') if version: d['supported_locales'] = filter( None, version.supported_locales.split(',')) else: d['supported_locales'] = [] d['tags'] = getattr(obj, 'tag_list', []) if obj.upsell and obj.upsell.premium.is_published(): upsell_obj = obj.upsell.premium d['upsell'] = { 'id': upsell_obj.id, 'app_slug': upsell_obj.app_slug, 'icon_url': upsell_obj.get_icon_url(128), # TODO: Store all localizations of upsell.name. 'name': unicode(upsell_obj.name), 'region_exclusions': upsell_obj.get_excluded_region_ids() } d['versions'] = [dict(version=v.version, resource_uri=reverse_version(v)) for v in obj.versions.all()] # Handle our localized fields. for field in ('description', 'homepage', 'name', 'support_email', 'support_url'): d['%s_translations' % field] = [ {'lang': to_language(lang), 'string': string} for lang, string in obj.translations[getattr(obj, '%s_id' % field)] if string] if version: attach_trans_dict(Version, [version]) d['release_notes_translations'] = [ {'lang': to_language(lang), 'string': string} for lang, string in version.translations[version.releasenotes_id]] else: d['release_notes_translations'] = None attach_trans_dict(Geodata, [geodata]) d['banner_message_translations'] = [ {'lang': to_language(lang), 'string': string} for lang, string in geodata.translations[geodata.banner_message_id]] for region in mkt.regions.ALL_REGION_IDS: d['popularity_%s' % region] = d['popularity'] # Bump the boost if the add-on is public. if obj.status == amo.STATUS_PUBLIC: d['boost'] = max(d['boost'], 1) * 4 # If the app is compatible with Firefox OS, push suggestion data in the # index - This will be used by RocketbarView API, which is specific to # Firefox OS. if DEVICE_GAIA.id in d['device'] and obj.is_published(): d['name_suggest'] = { 'input': d['name'], 'output': unicode(obj.id), # We only care about the payload. 'weight': d['boost'], 'payload': { 'default_locale': d['default_locale'], 'icon_hash': d['icon_hash'], 'id': d['id'], 'manifest_url': d['manifest_url'], 'modified': d['modified'], 'name_translations': d['name_translations'], 'slug': d['app_slug'], } } # Indices for each language. languages is a list of locales we want to # index with analyzer if the string's locale matches. for analyzer, languages in amo.SEARCH_ANALYZER_MAP.iteritems(): if (not settings.ES_USE_PLUGINS and analyzer in amo.SEARCH_ANALYZER_PLUGINS): continue d['name_' + analyzer] = list( set(string for locale, string in obj.translations[obj.name_id] if locale.lower() in languages)) d['description_' + analyzer] = list( set(string for locale, string in obj.translations[obj.description_id] if locale.lower() in languages)) return d
def test_correct_fields(self): fields = self.form.fields f_values = fields.values() assert 'version' not in fields assert all(isinstance(f, BooleanField) for f in f_values) self.assertSetEqual(fields, AppFeatures()._fields())
def extract_document(cls, pk=None, obj=None): """Extracts the ElasticSearch index document for this instance.""" from mkt.webapps.models import (AppFeatures, attach_devices, attach_prices, attach_translations, RatingDescriptors, RatingInteractives) if obj is None: obj = cls.get_model().objects.get(pk=pk) # Attach everything we need to index apps. for transform in (attach_devices, attach_prices, attach_tags, attach_translations): transform([obj]) latest_version = obj.latest_version version = obj.current_version features = (version.features.to_dict() if version else AppFeatures().to_dict()) try: status = latest_version.statuses[0][1] if latest_version else None except IndexError: status = None attrs = ('app_slug', 'bayesian_rating', 'created', 'default_locale', 'guid', 'hosted_url', 'icon_hash', 'id', 'is_disabled', 'is_offline', 'file_size', 'last_updated', 'modified', 'premium_type', 'promo_img_hash', 'status') d = dict(zip(attrs, attrgetter(*attrs)(obj))) d['app_type'] = obj.app_type_id d['author'] = obj.developer_name d['category'] = obj.categories if obj.categories else [] d['content_ratings'] = (obj.get_content_ratings_by_body(es=True) or None) try: d['content_descriptors'] = obj.rating_descriptors.to_keys() except RatingDescriptors.DoesNotExist: d['content_descriptors'] = [] d['current_version'] = version.version if version else None d['device'] = getattr(obj, 'device_ids', []) d['features'] = features d['has_public_stats'] = obj.public_stats try: d['interactive_elements'] = obj.rating_interactives.to_keys() except RatingInteractives.DoesNotExist: d['interactive_elements'] = [] d['installs_allowed_from'] = (version.manifest.get( 'installs_allowed_from', ['*']) if version else ['*']) d['is_priority'] = obj.priority_review is_escalated = obj.escalationqueue_set.exists() d['is_escalated'] = is_escalated d['escalation_date'] = (obj.escalationqueue_set.get().created if is_escalated else None) is_rereviewed = obj.rereviewqueue_set.exists() d['is_rereviewed'] = is_rereviewed d['rereview_date'] = (obj.rereviewqueue_set.get().created if is_rereviewed else None) if latest_version: d['latest_version'] = { 'status': status, 'is_privileged': latest_version.is_privileged, 'has_editor_comment': latest_version.has_editor_comment, 'has_info_request': latest_version.has_info_request, 'nomination_date': latest_version.nomination, 'created_date': latest_version.created, } else: d['latest_version'] = { 'status': None, 'is_privileged': None, 'has_editor_comment': None, 'has_info_request': None, 'nomination_date': None, 'created_date': None, } d['manifest_url'] = obj.get_manifest_url() d['package_path'] = obj.get_package_path() d['name_sort'] = unicode(obj.name).lower() d['owners'] = [ au.user.id for au in obj.addonuser_set.filter(role=mkt.AUTHOR_ROLE_OWNER) ] d['previews'] = [{ 'filetype': p.filetype, 'modified': p.modified, 'id': p.id, 'sizes': p.sizes } for p in obj.previews.all()] try: p = obj.addonpremium.price d['price_tier'] = p.name except AddonPremium.DoesNotExist: d['price_tier'] = None d['ratings'] = { 'average': obj.average_rating, 'count': obj.total_reviews, } d['region_exclusions'] = obj.get_excluded_region_ids() d['reviewed'] = obj.versions.filter(deleted=False).aggregate( Min('reviewed')).get('reviewed__min') # The default locale of the app is considered "supported" by default. supported_locales = [obj.default_locale] other_locales = (filter(None, version.supported_locales.split(',')) if version else []) if other_locales: supported_locales.extend(other_locales) d['supported_locales'] = list(set(supported_locales)) d['tags'] = getattr(obj, 'tags_list', []) d['tv_featured'] = obj.tags.filter(tag_text='featured-tv').exists() if obj.upsell and obj.upsell.premium.is_published(): upsell_obj = obj.upsell.premium d['upsell'] = { 'id': upsell_obj.id, 'app_slug': upsell_obj.app_slug, 'icon_url': upsell_obj.get_icon_url(128), # TODO: Store all localizations of upsell.name. 'name': unicode(upsell_obj.name), 'region_exclusions': upsell_obj.get_excluded_region_ids() } d['versions'] = [ dict(version=v.version, resource_uri=reverse_version(v)) for v in obj.versions.all() ] # Handle localized fields. # This adds both the field used for search and the one with # all translations for the API. for field in ('description', 'name'): d.update( cls.extract_field_translations(obj, field, include_field_for_search=True)) # This adds only the field with all the translations for the API, we # don't need to search on those. for field in ('homepage', 'support_email', 'support_url'): d.update(cls.extract_field_translations(obj, field)) if version: attach_trans_dict(version._meta.model, [version]) d.update( cls.extract_field_translations(version, 'release_notes', db_field='releasenotes_id')) else: d['release_notes_translations'] = None # Add boost, popularity, trending values. d.update(cls.extract_popularity_trending_boost(obj)) # If the app is compatible with Firefox OS, push suggestion data in the # index - This will be used by RocketbarView API, which is specific to # Firefox OS. if DEVICE_GAIA.id in d['device'] and obj.is_published(): d['name_suggest'] = { 'input': d['name'], 'output': unicode(obj.id), # We only care about the payload. 'weight': int(d['boost']), 'payload': { 'default_locale': d['default_locale'], 'icon_hash': d['icon_hash'], 'id': d['id'], 'manifest_url': d['manifest_url'], 'modified': d['modified'], 'name_translations': d['name_translations'], 'slug': d['app_slug'], } } for field in ('name', 'description'): d.update(cls.extract_field_analyzed_translations(obj, field)) return d
def test_bad_data(self): af = AppFeatures(version=self.app.current_version) af.set_flags('foo') af.set_flags('<script>')
def test_validator(self): update_features(ids=(self.app.pk, )) assert self.mock_validator.called features = self.app.current_version.features.to_dict() eq_(AppFeatures().to_dict(), features)
def _flag(self): "Flag app with a handful of feature flags for testing." af = AppFeatures(version=self.app.current_version) for f in self.flags: setattr(af, 'has_%s' % f.lower(), True) af.save()