class FileSerializer(serializers.ModelSerializer): url = serializers.SerializerMethodField() platform = ReverseChoiceField( choices=list(amo.PLATFORM_CHOICES_API.items())) status = ReverseChoiceField(choices=list(amo.STATUS_CHOICES_API.items())) permissions = serializers.ListField(child=serializers.CharField()) optional_permissions = serializers.ListField(child=serializers.CharField()) is_restart_required = serializers.BooleanField() class Meta: model = File fields = ( 'id', 'created', 'hash', 'is_restart_required', 'is_webextension', 'is_mozilla_signed_extension', 'platform', 'size', 'status', 'url', 'permissions', 'optional_permissions', ) def get_url(self, obj): return obj.get_absolute_url()
class AddonAbuseReportSerializer(BaseAbuseReportSerializer): addon = serializers.SerializerMethodField() reason = ReverseChoiceField(choices=list( AbuseReport.REASON_CHOICES_API.items()), required=False) application = ReverseChoiceField(choices=list( (v.id, k) for k, v in amo.APPS.items()), required=False) class Meta: model = AbuseReport fields = BaseAbuseReportSerializer.Meta.fields + ( 'addon', 'addon_install_entry_point', 'addon_install_method', 'addon_install_origin', 'addon_name', 'addon_signature', 'addon_summary', 'addon_version', 'application', 'application_locale', 'application_version', 'client_id', 'install_date', 'operating_system', 'operating_system_version', 'reason', ) def to_internal_value(self, data): self.validate_target(data, 'addon') view = self.context.get('view') output = { # get_guid() needs to be called first because get_addon_object() # would otherwise 404 on add-ons that don't match an existing # add-on in our database. 'guid': view.get_guid(), 'addon': view.get_addon_object(), } # Pop 'addon' before passing it to super(), we already have the # output value. data.pop('addon') output.update( super(AddonAbuseReportSerializer, self).to_internal_value(data)) return output def get_addon(self, obj): addon = obj.addon if not addon and not obj.guid: return None return { 'guid': addon.guid if addon else obj.guid, 'id': addon.id if addon else None, 'slug': addon.slug if addon else None, }
class AddonSerializer(serializers.ModelSerializer): current_version = VersionSerializer() description = TranslationSerializerField() edit_url = serializers.SerializerMethodField() homepage = TranslationSerializerField() name = TranslationSerializerField() review_url = serializers.SerializerMethodField() status = ReverseChoiceField(choices=amo.STATUS_CHOICES_API.items()) summary = TranslationSerializerField() support_email = TranslationSerializerField() support_url = TranslationSerializerField() tags = serializers.SerializerMethodField() type = ReverseChoiceField(choices=amo.ADDON_TYPE_CHOICES_API.items()) url = serializers.SerializerMethodField() # FIXME: # - categories (need to sort out the id/slug mess in existing search code) # - icon/previews # - average rating, number of downloads, hotness # - dictionary-specific things # - persona-specific things # - contributions-related things # - annoying/thankyou and related fields # - authors # - dependencies, site_specific, external_software # - thereason/thefuture (different endpoint ?) # - in collections, other add-ons by author, eula, privacy policy # - eula / privacy policy (different endpoint) # - all the reviewer/admin-specific fields (different serializer/endpoint) class Meta: model = Addon fields = ('id', 'current_version', 'default_locale', 'description', 'edit_url', 'guid', 'homepage', 'is_listed', 'name', 'last_updated', 'public_stats', 'review_url', 'slug', 'status', 'summary', 'support_email', 'support_url', 'tags', 'type', 'url') def get_tags(self, obj): if not hasattr(obj, 'tag_list'): attach_tags([obj]) # attach_tags() might not have attached anything to the addon, if it # had no tags. return getattr(obj, 'tag_list', []) def get_url(self, obj): return absolutify(obj.get_url_path()) def get_edit_url(self, obj): return absolutify(obj.get_dev_url()) def get_review_url(self, obj): return absolutify(reverse('editors.review', args=[obj.pk]))
class FileSerializer(serializers.ModelSerializer): url = serializers.SerializerMethodField() platform = ReverseChoiceField(choices=amo.PLATFORM_CHOICES_API.items()) status = ReverseChoiceField(choices=amo.STATUS_CHOICES_API.items()) class Meta: model = File fields = ('id', 'created', 'hash', 'platform', 'size', 'status', 'url') def get_url(self, obj): # File.get_url_path() is a little different, it's already absolute, but # needs a src parameter that is appended as a query string. return obj.get_url_path(src='')
class AddonFeatureCompatibilitySerializer(serializers.ModelSerializer): e10s = ReverseChoiceField( choices=amo.E10S_COMPATIBILITY_CHOICES_API.items()) class Meta: model = AddonFeatureCompatibility fields = ('e10s', )
class VersionSerializer(SimpleVersionSerializer): channel = ReverseChoiceField(choices=list(amo.CHANNEL_CHOICES_API.items()), read_only=True) license = SplitField( LicenseSlugSerializerField(required=False), LicenseSerializer(), ) class Meta: model = Version fields = ( 'id', 'channel', 'compatibility', 'edit_url', 'file', 'is_strict_compatibility_enabled', 'license', 'release_notes', 'reviewed', 'version', ) read_only_fields = fields def __init__(self, instance=None, data=serializers.empty, **kwargs): self.addon = kwargs.pop('addon', None) super().__init__(instance=instance, data=data, **kwargs)
class MinimalVersionSerializerWithChannel(MinimalVersionSerializer): channel = ReverseChoiceField( choices=list(amo.CHANNEL_CHOICES_API.items())) class Meta: model = Version fields = ('id', 'channel', 'version')
class VersionSerializer(SimpleVersionSerializer): channel = ReverseChoiceField(choices=amo.CHANNEL_CHOICES_API.items()) license = LicenseSerializer() release_notes = TranslationSerializerField(source='releasenotes') class Meta: model = Version fields = ('id', 'channel', 'compatibility', 'edit_url', 'files', 'license', 'release_notes', 'reviewed', 'url', 'version')
class VersionSerializer(SimpleVersionSerializer): channel = ReverseChoiceField(choices=list(amo.CHANNEL_CHOICES_API.items())) license = LicenseSerializer() class Meta: model = Version fields = ('id', 'channel', 'compatibility', 'edit_url', 'files', 'is_strict_compatibility_enabled', 'license', 'release_notes', 'reviewed', 'version')
class FileSerializer(serializers.ModelSerializer): url = serializers.SerializerMethodField() platform = ReverseChoiceField(choices=amo.PLATFORM_CHOICES_API.items()) status = ReverseChoiceField(choices=amo.STATUS_CHOICES_API.items()) permissions = serializers.ListField(source='webext_permissions_list', child=serializers.CharField()) is_restart_required = serializers.BooleanField() class Meta: model = File fields = ('id', 'created', 'hash', 'is_restart_required', 'is_webextension', 'is_mozilla_signed_extension', 'platform', 'size', 'status', 'url', 'permissions') def get_url(self, obj): # File.get_url_path() is a little different, it's already absolute, but # needs a src parameter that is appended as a query string. return obj.get_url_path(src='')
class FileSerializer(serializers.ModelSerializer): url = serializers.SerializerMethodField() platform = serializers.SerializerMethodField() status = ReverseChoiceField(choices=list(amo.STATUS_CHOICES_API.items())) permissions = serializers.ListField(child=serializers.CharField()) optional_permissions = serializers.ListField(child=serializers.CharField()) is_restart_required = serializers.SerializerMethodField() is_webextension = serializers.SerializerMethodField() class Meta: model = File fields = ( 'id', 'created', 'hash', 'is_restart_required', 'is_webextension', 'is_mozilla_signed_extension', 'platform', 'size', 'status', 'url', 'permissions', 'optional_permissions', ) def get_url(self, obj): return obj.get_absolute_url() def to_representation(self, obj): data = super().to_representation(obj) request = self.context.get('request', None) if request and not is_gate_active(request, 'platform-shim'): data.pop('platform', None) if request and not is_gate_active(request, 'is-restart-required-shim'): data.pop('is_restart_required', None) if request and not is_gate_active(request, 'is-webextension-shim'): data.pop('is_webextension', None) return data def get_platform(self, obj): # platform is gone, but we need to keep the API backwards compatible so # fake it by just returning 'all' all the time. return 'all' def get_is_restart_required(self, obj): # is_restart_required is gone from the model and all addons are restartless now # so fake it for older API clients with False return False def get_is_webextension(self, obj): # is_webextension is always True these days because all addons are webextensions # but fake it for older API clients. return True
class PromotedAddonSerializer(serializers.ModelSerializer): GROUP_CHOICES = [(group.id, group.api_name) for group in PROMOTED_GROUPS] apps = serializers.SerializerMethodField() category = ReverseChoiceField(choices=GROUP_CHOICES, source='group_id') class Meta: model = PromotedAddon fields = ( 'apps', 'category', ) def get_apps(self, obj): return [app.short for app in obj.approved_applications]
class FileSerializer(serializers.ModelSerializer): url = serializers.SerializerMethodField() platform = serializers.SerializerMethodField() status = ReverseChoiceField(choices=list(amo.STATUS_CHOICES_API.items())) permissions = serializers.ListField(child=serializers.CharField()) optional_permissions = serializers.ListField(child=serializers.CharField()) is_restart_required = serializers.BooleanField() class Meta: model = File fields = ( 'id', 'created', 'hash', 'is_restart_required', 'is_webextension', 'is_mozilla_signed_extension', 'platform', 'size', 'status', 'url', 'permissions', 'optional_permissions', ) def get_url(self, obj): return obj.get_absolute_url() def to_representation(self, obj): data = super().to_representation(obj) request = self.context.get('request', None) if request and not is_gate_active(request, 'platform-shim'): data.pop('platform', None) return data def get_platform(self, obj): # platform is gone, but we need to keep the API backwards compatible so # fake it by just returning 'all' all the time. return 'all'
class FileUploadSerializer(serializers.ModelSerializer): uuid = serializers.UUIDField(format='hex') channel = ReverseChoiceField( choices=[ (True, amo.CHANNEL_CHOICES_API[amo.RELEASE_CHANNEL_UNLISTED]), (False, amo.CHANNEL_CHOICES_API[amo.RELEASE_CHANNEL_LISTED]), ], source='automated_signing', ) processed = serializers.BooleanField() valid = serializers.BooleanField(source='passed_all_validations') validation = serializers.SerializerMethodField() url = serializers.SerializerMethodField() class Meta: model = FileUpload fields = [ 'uuid', 'channel', 'processed', 'submitted', 'url', 'valid', 'validation', 'version', ] def get_validation(self, instance): return instance.load_validation() if instance.validation else None def get_url(self, instance): return absolutify( drf_reverse( 'addon-upload-detail', request=self.context.get('request'), args=[instance.uuid.hex], ))
class ShelfSerializer(serializers.ModelSerializer): title = GetTextTranslationSerializerField() url = serializers.SerializerMethodField() footer = ShelfFooterField(source='*') addons = serializers.SerializerMethodField() addon_type = ReverseChoiceField( choices=list(amo.ADDON_TYPE_CHOICES_API.items())) class Meta: model = Shelf fields = [ 'title', 'url', 'endpoint', 'addon_type', 'criteria', 'footer', 'addons', ] def get_url(self, obj): if obj.endpoint == 'search': api = drf_reverse('addon-search', request=self.context.get('request')) url = api + obj.criteria elif obj.endpoint == 'collections': url = drf_reverse( 'collection-addon-list', request=self.context.get('request'), kwargs={ 'user_pk': str(settings.TASK_USER_ID), 'collection_slug': obj.criteria, }, ) else: url = None return url def get_addons(self, obj): request = self.context.get('request') if isinstance(request, DRFRequest): # rest framework wraps the underlying Request real_request = request._request else: real_request = request orginal_get = real_request.GET real_request.GET = real_request.GET.copy() if obj.endpoint == 'search': criteria = obj.criteria.strip('?') params = dict(parse.parse_qsl(criteria)) request.GET.update(params) addons = AddonSearchView(request=request).get_data(obj.get_count()) elif obj.endpoint == 'collections': kwargs = { 'user_pk': str(settings.TASK_USER_ID), 'collection_slug': obj.criteria, } collection_addons = CollectionAddonViewSet(request=request, action='list', kwargs=kwargs).get_data( obj.get_count()) addons = [ item['addon'] for item in collection_addons if 'addon' in item ] else: addons = None real_request.GET = orginal_get return addons
def test_to_internal_value_invalid_choices(self): """Test that choices still matter, and you can't a) send the internal value or b) send an invalid value.""" field = ReverseChoiceField(choices=(('internal', 'human'),)) with self.assertRaises(serializers.ValidationError): field.to_internal_value('internal')
class AddonAbuseReportSerializer(BaseAbuseReportSerializer): error_messages = { 'max_length': _( 'Please ensure this field has no more than {max_length} ' 'characters.' ) } addon = serializers.SerializerMethodField() reason = ReverseChoiceField( choices=list(AbuseReport.REASONS.api_choices), required=False, allow_null=True) # 'message' has custom validation rules below depending on whether 'reason' # was provided or not. We need to not set it as required and allow blank at # the field level to make that work. message = serializers.CharField( required=False, allow_blank=True, max_length=10000, error_messages=error_messages) app = ReverseChoiceField( choices=list((v.id, k) for k, v in amo.APPS.items()), required=False, source='application') appversion = serializers.CharField( required=False, source='application_version', max_length=255) lang = serializers.CharField( required=False, source='application_locale', max_length=255) report_entry_point = ReverseChoiceField( choices=list(AbuseReport.REPORT_ENTRY_POINTS.api_choices), required=False, allow_null=True) addon_install_method = ReverseChoiceField( choices=list(AbuseReport.ADDON_INSTALL_METHODS.api_choices), required=False, allow_null=True) addon_install_source = ReverseChoiceField( choices=list(AbuseReport.ADDON_INSTALL_SOURCES.api_choices), required=False, allow_null=True) addon_signature = ReverseChoiceField( choices=list(AbuseReport.ADDON_SIGNATURES.api_choices), required=False, allow_null=True) class Meta: model = AbuseReport fields = BaseAbuseReportSerializer.Meta.fields + ( 'addon', 'addon_install_method', 'addon_install_origin', 'addon_install_source', 'addon_install_source_url', 'addon_name', 'addon_signature', 'addon_summary', 'addon_version', 'app', 'appversion', 'client_id', 'install_date', 'lang', 'operating_system', 'operating_system_version', 'reason', 'report_entry_point' ) def validate(self, data): if not data.get('reason'): # If reason is not provided, message is required and can not be # null or blank. message = data.get('message') if not message: if 'message' not in data: msg = serializers.Field.default_error_messages['required'] elif message is None: msg = serializers.Field.default_error_messages['null'] else: msg = serializers.CharField.default_error_messages['blank'] raise serializers.ValidationError({ 'message': [msg] }) return data def handle_unknown_install_method_or_source(self, data, field_name): reversed_choices = self.fields[field_name].reversed_choices value = data[field_name] if value not in reversed_choices: log.warning('Unknown abuse report %s value submitted: %s', field_name, str(data[field_name])[:255]) value = 'other' return value def to_internal_value(self, data): # We want to accept unknown incoming data for `addon_install_method` # and `addon_install_source`, we have to transform it here, we can't # do it in a custom validation method because validation would be # skipped entirely if the value is not a valid choice. if 'addon_install_method' in data: data['addon_install_method'] = ( self.handle_unknown_install_method_or_source( data, 'addon_install_method')) if 'addon_install_source' in data: data['addon_install_source'] = ( self.handle_unknown_install_method_or_source( data, 'addon_install_source')) self.validate_target(data, 'addon') view = self.context.get('view') output = view.get_guid_and_addon() # Pop 'addon' from data before passing that data to super(), we already # have it in the output value. data.pop('addon') output.update( super(AddonAbuseReportSerializer, self).to_internal_value(data) ) return output def get_addon(self, obj): addon = obj.addon if not addon and not obj.guid: return None return { 'guid': addon.guid if addon else obj.guid, 'id': addon.id if addon else None, 'slug': addon.slug if addon else None, }
class AddonSerializer(serializers.ModelSerializer): authors = AddonDeveloperSerializer(many=True, source='listed_authors') categories = serializers.SerializerMethodField() contributions_url = serializers.URLField(source='contributions') current_version = CurrentVersionSerializer() description = TranslationSerializerField() developer_comments = TranslationSerializerField() edit_url = serializers.SerializerMethodField() has_eula = serializers.SerializerMethodField() has_privacy_policy = serializers.SerializerMethodField() homepage = TranslationSerializerField() icon_url = serializers.SerializerMethodField() icons = serializers.SerializerMethodField() is_source_public = serializers.SerializerMethodField() is_featured = serializers.SerializerMethodField() name = TranslationSerializerField() previews = PreviewSerializer(many=True, source='current_previews') ratings = serializers.SerializerMethodField() ratings_url = serializers.SerializerMethodField() review_url = serializers.SerializerMethodField() status = ReverseChoiceField(choices=list(amo.STATUS_CHOICES_API.items())) summary = TranslationSerializerField() support_email = TranslationSerializerField() support_url = TranslationSerializerField() tags = serializers.SerializerMethodField() type = ReverseChoiceField(choices=list(amo.ADDON_TYPE_CHOICES_API.items())) url = serializers.SerializerMethodField() class Meta: model = Addon fields = ('id', 'authors', 'average_daily_users', 'categories', 'contributions_url', 'created', 'current_version', 'default_locale', 'description', 'developer_comments', 'edit_url', 'guid', 'has_eula', 'has_privacy_policy', 'homepage', 'icon_url', 'icons', 'is_disabled', 'is_experimental', 'is_featured', 'is_recommended', 'is_source_public', 'last_updated', 'name', 'previews', 'public_stats', 'ratings', 'ratings_url', 'requires_payment', 'review_url', 'slug', 'status', 'summary', 'support_email', 'support_url', 'tags', 'type', 'url', 'weekly_downloads') def to_representation(self, obj): data = super(AddonSerializer, self).to_representation(obj) request = self.context.get('request', None) if ('request' in self.context and 'wrap_outgoing_links' in self.context['request'].GET): for key in ('homepage', 'support_url', 'contributions_url'): if key in data: data[key] = self.outgoingify(data[key]) if request and is_gate_active(request, 'del-addons-created-field'): data.pop('created', None) if request and not is_gate_active(request, 'is-source-public-shim'): data.pop('is_source_public', None) return data def outgoingify(self, data): if data: if isinstance(data, str): return get_outgoing_url(data) elif isinstance(data, dict): return { key: get_outgoing_url(value) if value else None for key, value in data.items() } # None or empty string... don't bother. return data def get_categories(self, obj): return { app_short_name: [cat.slug for cat in categories] for app_short_name, categories in obj.app_categories.items() } def get_has_eula(self, obj): return bool(getattr(obj, 'has_eula', obj.eula)) def get_is_featured(self, obj): # obj._is_featured is set from ES, so will only be present for list # requests. if not hasattr(obj, '_is_featured'): # Any featuring will do. obj._is_featured = obj.is_featured(app=None, lang=None) return obj._is_featured def get_has_privacy_policy(self, obj): return bool(getattr(obj, 'has_privacy_policy', obj.privacy_policy)) def get_tags(self, obj): if not hasattr(obj, 'tag_list'): attach_tags([obj]) # attach_tags() might not have attached anything to the addon, if it # had no tags. return getattr(obj, 'tag_list', []) def get_url(self, obj): # Use absolutify(get_detail_url()), get_absolute_url() calls # get_url_path() which does an extra check on current_version that is # annoying in subclasses which don't want to load that version. return absolutify(obj.get_detail_url()) def get_edit_url(self, obj): return absolutify(obj.get_dev_url()) def get_ratings_url(self, obj): return absolutify(obj.ratings_url) def get_review_url(self, obj): return absolutify(reverse('reviewers.review', args=[obj.pk])) def get_icon_url(self, obj): return absolutify(obj.get_icon_url(64)) def get_icons(self, obj): get_icon = obj.get_icon_url return { str(size): absolutify(get_icon(size)) for size in amo.ADDON_ICON_SIZES } def get_ratings(self, obj): return { 'average': obj.average_rating, 'bayesian_average': obj.bayesian_rating, 'count': obj.total_ratings, 'text_count': obj.text_ratings_count, } def get_is_source_public(self, obj): return False
class AddonSerializer(serializers.ModelSerializer): authors = AddonDeveloperSerializer(many=True, source='listed_authors') categories = serializers.SerializerMethodField() contributions_url = serializers.SerializerMethodField() current_version = CurrentVersionSerializer() description = TranslationSerializerField() developer_comments = TranslationSerializerField() edit_url = serializers.SerializerMethodField() has_eula = serializers.SerializerMethodField() has_privacy_policy = serializers.SerializerMethodField() homepage = TranslationSerializerField() icon_url = serializers.SerializerMethodField() icons = serializers.SerializerMethodField() is_source_public = serializers.SerializerMethodField() is_featured = serializers.SerializerMethodField() name = TranslationSerializerField() previews = PreviewSerializer(many=True, source='current_previews') promoted = PromotedAddonSerializer() ratings = serializers.SerializerMethodField() ratings_url = serializers.SerializerMethodField() review_url = serializers.SerializerMethodField() status = ReverseChoiceField(choices=list(amo.STATUS_CHOICES_API.items())) summary = TranslationSerializerField() support_email = TranslationSerializerField() support_url = TranslationSerializerField() tags = serializers.SerializerMethodField() type = ReverseChoiceField(choices=list(amo.ADDON_TYPE_CHOICES_API.items())) url = serializers.SerializerMethodField() class Meta: model = Addon fields = ('id', 'authors', 'average_daily_users', 'categories', 'contributions_url', 'created', 'current_version', 'default_locale', 'description', 'developer_comments', 'edit_url', 'guid', 'has_eula', 'has_privacy_policy', 'homepage', 'icon_url', 'icons', 'is_disabled', 'is_experimental', 'is_featured', 'is_source_public', 'last_updated', 'name', 'previews', 'promoted', 'ratings', 'ratings_url', 'requires_payment', 'review_url', 'slug', 'status', 'summary', 'support_email', 'support_url', 'tags', 'type', 'url', 'weekly_downloads') def to_representation(self, obj): data = super(AddonSerializer, self).to_representation(obj) request = self.context.get('request', None) if ('request' in self.context and 'wrap_outgoing_links' in self.context['request'].GET): for key in ('homepage', 'support_url', 'contributions_url'): if key in data: data[key] = self.outgoingify(data[key]) if request and is_gate_active(request, 'del-addons-created-field'): data.pop('created', None) if request and not is_gate_active(request, 'is-source-public-shim'): data.pop('is_source_public', None) if request and not is_gate_active(request, 'is-featured-addon-shim'): data.pop('is_featured', None) return data def outgoingify(self, data): if data: if isinstance(data, str): return get_outgoing_url(data) elif isinstance(data, dict): return { key: get_outgoing_url(value) if value else None for key, value in data.items() } # None or empty string... don't bother. return data def get_categories(self, obj): return { app_short_name: [cat.slug for cat in categories] for app_short_name, categories in obj.app_categories.items() } def get_has_eula(self, obj): return bool(getattr(obj, 'has_eula', obj.eula)) def get_is_featured(self, obj): # featured is gone, but we need to keep the API backwards compatible so # fake it with promoted status instead. return bool(obj.promoted and obj.promoted.group == RECOMMENDED) def get_has_privacy_policy(self, obj): return bool(getattr(obj, 'has_privacy_policy', obj.privacy_policy)) def get_tags(self, obj): if not hasattr(obj, 'tag_list'): attach_tags([obj]) # attach_tags() might not have attached anything to the addon, if it # had no tags. return getattr(obj, 'tag_list', []) def get_url(self, obj): # Use absolutify(get_detail_url()), get_absolute_url() calls # get_url_path() which does an extra check on current_version that is # annoying in subclasses which don't want to load that version. return absolutify(obj.get_detail_url()) def get_contributions_url(self, obj): if not obj.contributions: # don't add anything when it's not set. return obj.contributions parts = urlsplit(obj.contributions) query = QueryDict(parts.query, mutable=True) query.update(amo.CONTRIBUTE_UTM_PARAMS) return urlunsplit((parts.scheme, parts.netloc, parts.path, query.urlencode(), parts.fragment)) def get_edit_url(self, obj): return absolutify(obj.get_dev_url()) def get_ratings_url(self, obj): return absolutify(obj.ratings_url) def get_review_url(self, obj): return absolutify(reverse('reviewers.review', args=[obj.pk])) def get_icon_url(self, obj): return absolutify(obj.get_icon_url(64)) def get_icons(self, obj): get_icon = obj.get_icon_url return { str(size): absolutify(get_icon(size)) for size in amo.ADDON_ICON_SIZES } def get_ratings(self, obj): return { 'average': obj.average_rating, 'bayesian_average': obj.bayesian_rating, 'count': obj.total_ratings, 'text_count': obj.text_ratings_count, } def get_is_source_public(self, obj): return False
class AddonSerializer(serializers.ModelSerializer): current_version = VersionSerializer() description = TranslationSerializerField() edit_url = serializers.SerializerMethodField() homepage = TranslationSerializerField() icon_url = serializers.SerializerMethodField() name = TranslationSerializerField() review_url = serializers.SerializerMethodField() status = ReverseChoiceField(choices=amo.STATUS_CHOICES_API.items()) summary = TranslationSerializerField() support_email = TranslationSerializerField() support_url = TranslationSerializerField() tags = serializers.SerializerMethodField() theme_data = serializers.SerializerMethodField() type = ReverseChoiceField(choices=amo.ADDON_TYPE_CHOICES_API.items()) url = serializers.SerializerMethodField() # FIXME: # - categories (need to sort out the id/slug mess in existing search code) # - previews # - average rating, number of downloads, hotness # - dictionary-specific things # - persona-specific things # - contributions-related things # - annoying/thankyou and related fields # - authors # - dependencies, site_specific, external_software # - thereason/thefuture (different endpoint ?) # - in collections, other add-ons by author, eula, privacy policy # - eula / privacy policy (different endpoint) # - all the reviewer/admin-specific fields (different serializer/endpoint) class Meta: model = Addon fields = ('id', 'current_version', 'default_locale', 'description', 'edit_url', 'guid', 'homepage', 'icon_url', 'is_listed', 'name', 'last_updated', 'public_stats', 'review_url', 'slug', 'status', 'summary', 'support_email', 'support_url', 'tags', 'theme_data', 'type', 'url') def to_representation(self, obj): data = super(AddonSerializer, self).to_representation(obj) if data['theme_data'] is None: data.pop('theme_data') return data def get_tags(self, obj): if not hasattr(obj, 'tag_list'): attach_tags([obj]) # attach_tags() might not have attached anything to the addon, if it # had no tags. return getattr(obj, 'tag_list', []) def get_url(self, obj): return absolutify(obj.get_url_path()) def get_edit_url(self, obj): return absolutify(obj.get_dev_url()) def get_review_url(self, obj): return absolutify(reverse('editors.review', args=[obj.pk])) def get_icon_url(self, obj): if self.is_broken_persona(obj): return absolutify(obj.get_default_icon_url(64)) return absolutify(obj.get_icon_url(64)) def get_theme_data(self, obj): theme_data = None if obj.type == amo.ADDON_PERSONA and not self.is_broken_persona(obj): theme_data = obj.persona.theme_data return theme_data def is_broken_persona(self, obj): """Find out if the object is a Persona and either is missing its Persona instance or has a broken one. Call this everytime something in the serializer is suceptible to call something on the Persona instance, explicitely or not, to avoid 500 errors and/or SQL queries in ESAddonSerializer.""" try: # Sadly, https://code.djangoproject.com/ticket/14368 prevents us # from setting obj.persona = None in ESAddonSerializer.fake_object # below. This is fixed in Django 1.9, but in the meantime we work # around it by creating a Persona instance with a custom '_broken' # attribute indicating that it should not be used. if obj.type == amo.ADDON_PERSONA and ( obj.persona is None or hasattr(obj.persona, '_broken')): raise Persona.DoesNotExist except Persona.DoesNotExist: # We got a DoesNotExist exception, therefore the Persona does not # exist or is broken. return True # Everything is fine, move on. return False
class AddonSerializer(serializers.ModelSerializer): authors = AddonDeveloperSerializer(many=True, source='listed_authors') categories = serializers.SerializerMethodField() contributions_url = ContributionSerializerField(source='contributions') current_version = CurrentVersionSerializer() description = TranslationSerializerField() developer_comments = TranslationSerializerField() edit_url = serializers.SerializerMethodField() has_eula = serializers.SerializerMethodField() has_privacy_policy = serializers.SerializerMethodField() homepage = OutgoingTranslationField() icon_url = serializers.SerializerMethodField() icons = serializers.SerializerMethodField() is_source_public = serializers.SerializerMethodField() is_featured = serializers.SerializerMethodField() name = TranslationSerializerField() previews = PreviewSerializer(many=True, source='current_previews') promoted = PromotedAddonSerializer() ratings = serializers.SerializerMethodField() ratings_url = serializers.SerializerMethodField() review_url = serializers.SerializerMethodField() status = ReverseChoiceField(choices=list(amo.STATUS_CHOICES_API.items())) summary = TranslationSerializerField() support_email = TranslationSerializerField() support_url = OutgoingTranslationField() tags = serializers.SerializerMethodField() type = ReverseChoiceField(choices=list(amo.ADDON_TYPE_CHOICES_API.items())) url = serializers.SerializerMethodField() versions_url = serializers.SerializerMethodField() class Meta: model = Addon fields = ( 'id', 'authors', 'average_daily_users', 'categories', 'contributions_url', 'created', 'current_version', 'default_locale', 'description', 'developer_comments', 'edit_url', 'guid', 'has_eula', 'has_privacy_policy', 'homepage', 'icon_url', 'icons', 'is_disabled', 'is_experimental', 'is_featured', 'is_source_public', 'last_updated', 'name', 'previews', 'promoted', 'ratings', 'ratings_url', 'requires_payment', 'review_url', 'slug', 'status', 'summary', 'support_email', 'support_url', 'tags', 'type', 'url', 'versions_url', 'weekly_downloads', ) def to_representation(self, obj): data = super(AddonSerializer, self).to_representation(obj) request = self.context.get('request', None) if request and is_gate_active(request, 'del-addons-created-field'): data.pop('created', None) if request and not is_gate_active(request, 'is-source-public-shim'): data.pop('is_source_public', None) if request and not is_gate_active(request, 'is-featured-addon-shim'): data.pop('is_featured', None) return data def get_categories(self, obj): return { app_short_name: [cat.slug for cat in categories] for app_short_name, categories in obj.app_categories.items() } def get_has_eula(self, obj): return bool(getattr(obj, 'has_eula', obj.eula)) def get_is_featured(self, obj): # featured is gone, but we need to keep the API backwards compatible so # fake it with promoted status instead. return bool(obj.promoted and obj.promoted.group == RECOMMENDED) def get_has_privacy_policy(self, obj): return bool(getattr(obj, 'has_privacy_policy', obj.privacy_policy)) def get_tags(self, obj): if not hasattr(obj, 'tag_list'): attach_tags([obj]) # attach_tags() might not have attached anything to the addon, if it # had no tags. return getattr(obj, 'tag_list', []) def get_url(self, obj): # Use absolutify(get_detail_url()), get_absolute_url() calls # get_url_path() which does an extra check on current_version that is # annoying in subclasses which don't want to load that version. return absolutify(obj.get_detail_url()) def get_edit_url(self, obj): return absolutify(obj.get_dev_url()) def get_ratings_url(self, obj): return absolutify(obj.ratings_url) def get_versions_url(self, obj): return absolutify(obj.versions_url) def get_review_url(self, obj): return absolutify(reverse('reviewers.review', args=[obj.pk])) def get_icon_url(self, obj): return absolutify(obj.get_icon_url(64)) def get_icons(self, obj): get_icon = obj.get_icon_url return { str(size): absolutify(get_icon(size)) for size in amo.ADDON_ICON_SIZES } def get_ratings(self, obj): ratings = { 'average': obj.average_rating, 'bayesian_average': obj.bayesian_rating, 'count': obj.total_ratings, 'text_count': obj.text_ratings_count, } if (request := self.context.get( 'request', None)) and (grouped := get_grouped_ratings( request, obj)):
class AddonAbuseReportSerializer(BaseAbuseReportSerializer): addon = serializers.SerializerMethodField() reason = ReverseChoiceField( choices=list(AbuseReport.REASONS.api_choices), required=False, allow_null=True) app = ReverseChoiceField( choices=list((v.id, k) for k, v in amo.APPS.items()), required=False, source='application') appversion = serializers.CharField( required=False, source='application_version', max_length=255) lang = serializers.CharField( required=False, source='application_locale', max_length=255) report_entry_point = ReverseChoiceField( choices=list(AbuseReport.REPORT_ENTRY_POINTS.api_choices), required=False, allow_null=True) addon_install_method = ReverseChoiceField( choices=list(AbuseReport.ADDON_INSTALL_METHODS.api_choices), required=False, allow_null=True) addon_signature = ReverseChoiceField( choices=list(AbuseReport.ADDON_SIGNATURES.api_choices), required=False, allow_null=True) class Meta: model = AbuseReport fields = BaseAbuseReportSerializer.Meta.fields + ( 'addon', 'addon_install_method', 'addon_install_origin', 'addon_name', 'addon_signature', 'addon_summary', 'addon_version', 'app', 'appversion', 'client_id', 'install_date', 'lang', 'operating_system', 'operating_system_version', 'reason', 'report_entry_point' ) def to_internal_value(self, data): self.validate_target(data, 'addon') view = self.context.get('view') output = view.get_guid_and_addon() # Pop 'addon' from data before passing that data to super(), we already # have it in the output value. data.pop('addon') output.update( super(AddonAbuseReportSerializer, self).to_internal_value(data) ) return output def get_addon(self, obj): addon = obj.addon if not addon and not obj.guid: return None return { 'guid': addon.guid if addon else obj.guid, 'id': addon.id if addon else None, 'slug': addon.slug if addon else None, }
class ShelfSerializer(serializers.ModelSerializer): title = GetTextTranslationSerializerField() url = serializers.SerializerMethodField() footer = ShelfFooterField(source='*') addons = serializers.SerializerMethodField() addon_type = ReverseChoiceField( choices=list(amo.ADDON_TYPE_CHOICES_API.items())) class Meta: model = Shelf fields = [ 'title', 'url', 'endpoint', 'addon_type', 'footer', 'addons', ] def to_representation(self, obj): data = super().to_representation(obj) if obj.endpoint == Shelf.Endpoints.RANDOM_TAG: # Replace {tag} token in title and footer text data['title'] = { locale: (value.replace('{tag}', obj.tag) if value is not None else None) for locale, value in (data.get('title') or {}).items() } or None data['footer']['text'] = { locale: (value.replace('{tag}', obj.tag) if value is not None else None) for locale, value in ( (data.get('footer') or {}).get('text') or {}).items() } or None return data def get_url(self, obj): if obj.endpoint in (Shelf.Endpoints.SEARCH, Shelf.Endpoints.RANDOM_TAG): api = drf_reverse('addon-search', request=self.context.get('request')) params = obj.get_param_dict() url = f'{api}?{"&".join(f"{key}={value}" for key, value in params.items())}' elif obj.endpoint == Shelf.Endpoints.COLLECTIONS: url = drf_reverse( 'collection-addon-list', request=self.context.get('request'), kwargs={ 'user_pk': str(settings.TASK_USER_ID), 'collection_slug': obj.criteria, }, ) else: url = None return url def get_addons(self, obj): request = self.context.get('request') if isinstance(request, DRFRequest): # rest framework wraps the underlying Request real_request = request._request else: real_request = request orginal_get = real_request.GET real_request.GET = real_request.GET.copy() if obj.endpoint in (Shelf.Endpoints.SEARCH, Shelf.Endpoints.RANDOM_TAG): request.GET.update(obj.get_param_dict()) addons = AddonSearchView(request=request).get_data(obj.get_count()) elif obj.endpoint == Shelf.Endpoints.COLLECTIONS: kwargs = { 'user_pk': str(settings.TASK_USER_ID), 'collection_slug': obj.criteria, } collection_addons = CollectionAddonViewSet(request=request, action='list', kwargs=kwargs).get_data( obj.get_count()) addons = [ item['addon'] for item in collection_addons if 'addon' in item ] else: addons = [] real_request.GET = orginal_get return addons
def test_to_representation(self): """Test that when we return a reprensentation to the client, we convert the internal value in an human-readable format (e.g. a string constant).""" field = ReverseChoiceField(choices=(('internal', 'human'),)) assert field.to_representation('internal') == 'human'
def test_to_internal_value(self): """Test that when a client sends data in human-readable format (e.g. a string constant), we convert it to the internal format when converting data to internal value.""" field = ReverseChoiceField(choices=(('internal', 'human'),)) assert field.to_internal_value('human') == 'internal'
class AddonSerializer(serializers.ModelSerializer): authors = BaseUserSerializer(many=True, source='listed_authors') categories = serializers.SerializerMethodField() current_beta_version = SimpleVersionSerializer() current_version = SimpleVersionSerializer() description = TranslationSerializerField() edit_url = serializers.SerializerMethodField() has_eula = serializers.SerializerMethodField() has_privacy_policy = serializers.SerializerMethodField() homepage = TranslationSerializerField() icon_url = serializers.SerializerMethodField() is_source_public = serializers.BooleanField(source='view_source') name = TranslationSerializerField() previews = PreviewSerializer(many=True, source='all_previews') ratings = serializers.SerializerMethodField() review_url = serializers.SerializerMethodField() status = ReverseChoiceField(choices=amo.STATUS_CHOICES_API.items()) summary = TranslationSerializerField() support_email = TranslationSerializerField() support_url = TranslationSerializerField() tags = serializers.SerializerMethodField() theme_data = serializers.SerializerMethodField() type = ReverseChoiceField(choices=amo.ADDON_TYPE_CHOICES_API.items()) url = serializers.SerializerMethodField() class Meta: model = Addon fields = ('id', 'authors', 'average_daily_users', 'categories', 'current_beta_version', 'current_version', 'default_locale', 'description', 'edit_url', 'guid', 'has_eula', 'has_privacy_policy', 'homepage', 'icon_url', 'is_disabled', 'is_experimental', 'is_source_public', 'last_updated', 'name', 'previews', 'public_stats', 'ratings', 'review_url', 'slug', 'status', 'summary', 'support_email', 'support_url', 'tags', 'theme_data', 'type', 'url', 'weekly_downloads') def to_representation(self, obj): data = super(AddonSerializer, self).to_representation(obj) if 'theme_data' in data and data['theme_data'] is None: data.pop('theme_data') if 'homepage' in data: data['homepage'] = self.outgoingify(data['homepage']) if 'support_url' in data: data['support_url'] = self.outgoingify(data['support_url']) return data def outgoingify(self, data): if isinstance(data, basestring): return get_outgoing_url(data) elif isinstance(data, dict): return { key: get_outgoing_url(value) if value else None for key, value in data.items() } # Probably None... don't bother. return data def get_categories(self, obj): # Return a dict of lists like obj.app_categories does, but exposing # slugs for keys and values instead of objects. return { app.short: [cat.slug for cat in obj.app_categories[app]] for app in obj.app_categories.keys() } def get_has_eula(self, obj): return bool(getattr(obj, 'has_eula', obj.eula)) def get_has_privacy_policy(self, obj): return bool(getattr(obj, 'has_privacy_policy', obj.privacy_policy)) def get_tags(self, obj): if not hasattr(obj, 'tag_list'): attach_tags([obj]) # attach_tags() might not have attached anything to the addon, if it # had no tags. return getattr(obj, 'tag_list', []) def get_url(self, obj): return absolutify(obj.get_url_path()) def get_edit_url(self, obj): return absolutify(obj.get_dev_url()) def get_review_url(self, obj): return absolutify(reverse('editors.review', args=[obj.pk])) def get_icon_url(self, obj): if self.is_broken_persona(obj): return absolutify(obj.get_default_icon_url(64)) return absolutify(obj.get_icon_url(64)) def get_ratings(self, obj): return { 'average': obj.average_rating, 'count': obj.total_reviews, } def get_theme_data(self, obj): theme_data = None if obj.type == amo.ADDON_PERSONA and not self.is_broken_persona(obj): theme_data = obj.persona.theme_data return theme_data def is_broken_persona(self, obj): """Find out if the object is a Persona and either is missing its Persona instance or has a broken one. Call this everytime something in the serializer is suceptible to call something on the Persona instance, explicitly or not, to avoid 500 errors and/or SQL queries in ESAddonSerializer.""" try: # Sadly, https://code.djangoproject.com/ticket/14368 prevents us # from setting obj.persona = None in ESAddonSerializer.fake_object # below. This is fixed in Django 1.9, but in the meantime we work # around it by creating a Persona instance with a custom '_broken' # attribute indicating that it should not be used. if obj.type == amo.ADDON_PERSONA and ( obj.persona is None or hasattr(obj.persona, '_broken')): raise Persona.DoesNotExist except Persona.DoesNotExist: # We got a DoesNotExist exception, therefore the Persona does not # exist or is broken. return True # Everything is fine, move on. return False
class AddonSerializer(serializers.ModelSerializer): authors = AddonDeveloperSerializer(many=True, source='listed_authors') categories = serializers.SerializerMethodField() contributions_url = serializers.URLField(source='contributions') current_beta_version = SimpleVersionSerializer() current_version = SimpleVersionSerializer() description = TranslationSerializerField() developer_comments = TranslationSerializerField() edit_url = serializers.SerializerMethodField() has_eula = serializers.SerializerMethodField() has_privacy_policy = serializers.SerializerMethodField() homepage = TranslationSerializerField() icon_url = serializers.SerializerMethodField() icons = serializers.SerializerMethodField() is_source_public = serializers.BooleanField(source='view_source') is_featured = serializers.SerializerMethodField() name = TranslationSerializerField() previews = PreviewSerializer(many=True, source='all_previews') ratings = serializers.SerializerMethodField() review_url = serializers.SerializerMethodField() status = ReverseChoiceField(choices=amo.STATUS_CHOICES_API.items()) summary = TranslationSerializerField() support_email = TranslationSerializerField() support_url = TranslationSerializerField() tags = serializers.SerializerMethodField() theme_data = serializers.SerializerMethodField() type = ReverseChoiceField(choices=amo.ADDON_TYPE_CHOICES_API.items()) url = serializers.SerializerMethodField() class Meta: model = Addon fields = ( 'id', 'authors', 'average_daily_users', 'categories', 'contributions_url', 'current_beta_version', 'current_version', 'default_locale', 'description', 'developer_comments', 'edit_url', 'guid', 'has_eula', 'has_privacy_policy', 'homepage', 'icon_url', 'icons', 'is_disabled', 'is_experimental', 'is_featured', 'is_source_public', 'last_updated', 'name', 'previews', 'public_stats', 'ratings', 'ratings_url', 'requires_payment', 'review_url', 'slug', 'status', 'summary', 'support_email', 'support_url', 'tags', 'theme_data', 'type', 'url', 'weekly_downloads' ) def to_representation(self, obj): data = super(AddonSerializer, self).to_representation(obj) if 'theme_data' in data and data['theme_data'] is None: data.pop('theme_data') if ('request' in self.context and 'wrap_outgoing_links' in self.context['request'].GET): for key in ('homepage', 'support_url', 'contributions_url'): if key in data: data[key] = self.outgoingify(data[key]) if obj.type == amo.ADDON_PERSONA: if 'weekly_downloads' in data: # weekly_downloads don't make sense for lightweight themes. data.pop('weekly_downloads') if ('average_daily_users' in data and not self.is_broken_persona(obj)): # In addition, their average_daily_users number must come from # the popularity field of the attached Persona. data['average_daily_users'] = obj.persona.popularity return data def outgoingify(self, data): if isinstance(data, basestring): return get_outgoing_url(data) elif isinstance(data, dict): return {key: get_outgoing_url(value) if value else None for key, value in data.items()} # Probably None... don't bother. return data def get_categories(self, obj): # Return a dict of lists like obj.app_categories does, but exposing # slugs for keys and values instead of objects. return { app.short: [cat.slug for cat in obj.app_categories[app]] for app in obj.app_categories.keys() } def get_has_eula(self, obj): return bool(getattr(obj, 'has_eula', obj.eula)) def get_is_featured(self, obj): # obj._is_featured is set from ES, so will only be present for list # requests. if not hasattr(obj, '_is_featured'): # Any featuring will do. obj._is_featured = obj.is_featured(app=None, lang=None) return obj._is_featured def get_has_privacy_policy(self, obj): return bool(getattr(obj, 'has_privacy_policy', obj.privacy_policy)) def get_tags(self, obj): if not hasattr(obj, 'tag_list'): attach_tags([obj]) # attach_tags() might not have attached anything to the addon, if it # had no tags. return getattr(obj, 'tag_list', []) def get_url(self, obj): return absolutify(obj.get_url_path()) def get_edit_url(self, obj): return absolutify(obj.get_dev_url()) def get_review_url(self, obj): return absolutify(reverse('reviewers.review', args=[obj.pk])) def get_icon_url(self, obj): if self.is_broken_persona(obj): return absolutify(obj.get_default_icon_url(64)) return absolutify(obj.get_icon_url(64)) def get_icons(self, obj): # We're using only 32 and 64 for compatibility reasons with the # old search API. https://github.com/mozilla/addons-server/issues/7514 if self.is_broken_persona(obj): get_icon = obj.get_default_icon_url else: get_icon = obj.get_icon_url return {str(size): absolutify(get_icon(size)) for size in (32, 64)} def get_ratings(self, obj): return { 'average': obj.average_rating, 'bayesian_average': obj.bayesian_rating, 'count': obj.total_ratings, 'text_count': obj.text_ratings_count, } def get_theme_data(self, obj): theme_data = None if obj.type == amo.ADDON_PERSONA and not self.is_broken_persona(obj): theme_data = obj.persona.theme_data return theme_data def is_broken_persona(self, obj): """Find out if the object is a Persona and either is missing its Persona instance or has a broken one. Call this everytime something in the serializer is suceptible to call something on the Persona instance, explicitly or not, to avoid 500 errors and/or SQL queries in ESAddonSerializer.""" try: # Sadly, https://code.djangoproject.com/ticket/14368 prevents us # from setting obj.persona = None in ESAddonSerializer.fake_object # below. This is fixed in Django 1.9, but in the meantime we work # around it by creating a Persona instance with a custom '_broken' # attribute indicating that it should not be used. if obj.type == amo.ADDON_PERSONA and ( obj.persona is None or hasattr(obj.persona, '_broken')): raise Persona.DoesNotExist except Persona.DoesNotExist: # We got a DoesNotExist exception, therefore the Persona does not # exist or is broken. return True # Everything is fine, move on. return False
class AddonSerializer(serializers.ModelSerializer): authors = AddonDeveloperSerializer(many=True, source='listed_authors', read_only=True) categories = CategoriesSerializerField(source='all_categories', required=False) contributions_url = ContributionSerializerField(source='contributions', read_only=True) current_version = CurrentVersionSerializer(read_only=True) description = TranslationSerializerField(required=False) developer_comments = TranslationSerializerField(required=False) edit_url = serializers.SerializerMethodField() has_eula = serializers.SerializerMethodField() has_privacy_policy = serializers.SerializerMethodField() homepage = OutgoingURLTranslationField(required=False) icon_url = serializers.SerializerMethodField() icons = serializers.SerializerMethodField() is_disabled = SplitField( serializers.BooleanField(source='disabled_by_user', required=False), serializers.BooleanField(), ) is_source_public = serializers.SerializerMethodField() is_featured = serializers.SerializerMethodField() name = TranslationSerializerField(required=False, max_length=50) previews = PreviewSerializer(many=True, source='current_previews', read_only=True) promoted = PromotedAddonSerializer(read_only=True) ratings = serializers.SerializerMethodField() ratings_url = serializers.SerializerMethodField() review_url = serializers.SerializerMethodField() status = ReverseChoiceField(choices=list(amo.STATUS_CHOICES_API.items()), read_only=True) summary = TranslationSerializerField(required=False, max_length=250) support_email = EmailTranslationField(required=False) support_url = OutgoingURLTranslationField(required=False) tags = serializers.ListField( child=LazyChoiceField( choices=Tag.objects.values_list('tag_text', flat=True)), max_length=amo.MAX_TAGS, source='tag_list', required=False, ) type = ReverseChoiceField(choices=list(amo.ADDON_TYPE_CHOICES_API.items()), read_only=True) url = serializers.SerializerMethodField() version = DeveloperVersionSerializer(write_only=True) versions_url = serializers.SerializerMethodField() class Meta: model = Addon fields = ( 'id', 'authors', 'average_daily_users', 'categories', 'contributions_url', 'created', 'current_version', 'default_locale', 'description', 'developer_comments', 'edit_url', 'guid', 'has_eula', 'has_privacy_policy', 'homepage', 'icon_url', 'icons', 'is_disabled', 'is_experimental', 'is_featured', 'is_source_public', 'last_updated', 'name', 'previews', 'promoted', 'ratings', 'ratings_url', 'requires_payment', 'review_url', 'slug', 'status', 'summary', 'support_email', 'support_url', 'tags', 'type', 'url', 'version', 'versions_url', 'weekly_downloads', ) writeable_fields = ( 'categories', 'description', 'developer_comments', 'homepage', 'is_disabled', 'is_experimental', 'name', 'requires_payment', 'slug', 'summary', 'support_email', 'support_url', 'tags', 'version', ) read_only_fields = tuple(set(fields) - set(writeable_fields)) def __init__(self, instance=None, data=serializers.empty, **kwargs): if instance and isinstance(data, dict): data.pop('version', None) # we only support version field for create super().__init__(instance=instance, data=data, **kwargs) def to_representation(self, obj): data = super().to_representation(obj) request = self.context.get('request', None) if request and is_gate_active(request, 'del-addons-created-field'): data.pop('created', None) if request and not is_gate_active(request, 'is-source-public-shim'): data.pop('is_source_public', None) if request and not is_gate_active(request, 'is-featured-addon-shim'): data.pop('is_featured', None) return data def get_has_eula(self, obj): return bool(getattr(obj, 'has_eula', obj.eula)) def get_is_featured(self, obj): # featured is gone, but we need to keep the API backwards compatible so # fake it with promoted status instead. return bool(obj.promoted and obj.promoted.group == RECOMMENDED) def get_has_privacy_policy(self, obj): return bool(getattr(obj, 'has_privacy_policy', obj.privacy_policy)) def get_url(self, obj): # Use absolutify(get_detail_url()), get_absolute_url() calls # get_url_path() which does an extra check on current_version that is # annoying in subclasses which don't want to load that version. return absolutify(obj.get_detail_url()) def get_edit_url(self, obj): return absolutify(obj.get_dev_url()) def get_ratings_url(self, obj): return absolutify(obj.ratings_url) def get_versions_url(self, obj): return absolutify(obj.versions_url) def get_review_url(self, obj): return absolutify(reverse('reviewers.review', args=[obj.pk])) def get_icon_url(self, obj): return absolutify(obj.get_icon_url(64)) def get_icons(self, obj): get_icon = obj.get_icon_url return { str(size): absolutify(get_icon(size)) for size in amo.ADDON_ICON_SIZES } def get_ratings(self, obj): ratings = { 'average': obj.average_rating, 'bayesian_average': obj.bayesian_rating, 'count': obj.total_ratings, 'text_count': obj.text_ratings_count, } if (request := self.context.get( 'request', None)) and (grouped := get_grouped_ratings( request, obj)):
class AddonAbuseReportSerializer(BaseAbuseReportSerializer): addon = serializers.SerializerMethodField() reason = ReverseChoiceField(choices=list(AbuseReport.REASONS.api_choices), required=False, allow_null=True) # 'message' has custom validation rules below depending on whether 'reason' # was provided or not. We need to not set it as required and allow blank at # the field level to make that work. message = serializers.CharField(required=False, allow_blank=True) app = ReverseChoiceField(choices=list( (v.id, k) for k, v in amo.APPS.items()), required=False, source='application') appversion = serializers.CharField(required=False, source='application_version', max_length=255) lang = serializers.CharField(required=False, source='application_locale', max_length=255) report_entry_point = ReverseChoiceField(choices=list( AbuseReport.REPORT_ENTRY_POINTS.api_choices), required=False, allow_null=True) addon_install_method = ReverseChoiceField(choices=list( AbuseReport.ADDON_INSTALL_METHODS.api_choices), required=False, allow_null=True) addon_signature = ReverseChoiceField(choices=list( AbuseReport.ADDON_SIGNATURES.api_choices), required=False, allow_null=True) class Meta: model = AbuseReport fields = BaseAbuseReportSerializer.Meta.fields + ( 'addon', 'addon_install_method', 'addon_install_origin', 'addon_name', 'addon_signature', 'addon_summary', 'addon_version', 'app', 'appversion', 'client_id', 'install_date', 'lang', 'operating_system', 'operating_system_version', 'reason', 'report_entry_point') def validate(self, data): if not data.get('reason'): # If reason is not provided, message is required and can not be # null or blank. message = data.get('message') if not message: if 'message' not in data: msg = serializers.Field.default_error_messages['required'] elif message is None: msg = serializers.Field.default_error_messages['null'] else: msg = serializers.CharField.default_error_messages['blank'] raise serializers.ValidationError({'message': [msg]}) return data def to_internal_value(self, data): self.validate_target(data, 'addon') view = self.context.get('view') output = view.get_guid_and_addon() # Pop 'addon' from data before passing that data to super(), we already # have it in the output value. data.pop('addon') output.update( super(AddonAbuseReportSerializer, self).to_internal_value(data)) return output def get_addon(self, obj): addon = obj.addon if not addon and not obj.guid: return None return { 'guid': addon.guid if addon else obj.guid, 'id': addon.id if addon else None, 'slug': addon.slug if addon else None, }
class AddonSerializer(serializers.ModelSerializer): authors = AddonDeveloperSerializer(many=True, source='listed_authors') categories = serializers.SerializerMethodField() contributions_url = serializers.URLField(source='contributions') current_version = CurrentVersionSerializer() description = TranslationSerializerField() developer_comments = TranslationSerializerField() edit_url = serializers.SerializerMethodField() has_eula = serializers.SerializerMethodField() has_privacy_policy = serializers.SerializerMethodField() homepage = TranslationSerializerField() icon_url = serializers.SerializerMethodField() icons = serializers.SerializerMethodField() is_source_public = serializers.BooleanField(source='view_source') is_featured = serializers.SerializerMethodField() name = TranslationSerializerField() previews = PreviewSerializer(many=True, source='current_previews') ratings = serializers.SerializerMethodField() ratings_url = serializers.SerializerMethodField() review_url = serializers.SerializerMethodField() status = ReverseChoiceField(choices=list(amo.STATUS_CHOICES_API.items())) summary = TranslationSerializerField() support_email = TranslationSerializerField() support_url = TranslationSerializerField() tags = serializers.SerializerMethodField() theme_data = serializers.SerializerMethodField() type = ReverseChoiceField(choices=list(amo.ADDON_TYPE_CHOICES_API.items())) url = serializers.SerializerMethodField() class Meta: model = Addon fields = ( 'id', 'authors', 'average_daily_users', 'categories', 'contributions_url', 'created', 'current_version', 'default_locale', 'description', 'developer_comments', 'edit_url', 'guid', 'has_eula', 'has_privacy_policy', 'homepage', 'icon_url', 'icons', 'is_disabled', 'is_experimental', 'is_featured', 'is_recommended', 'is_source_public', 'last_updated', 'name', 'previews', 'public_stats', 'ratings', 'ratings_url', 'requires_payment', 'review_url', 'slug', 'status', 'summary', 'support_email', 'support_url', 'tags', 'theme_data', 'type', 'url', 'weekly_downloads' ) def to_representation(self, obj): data = super(AddonSerializer, self).to_representation(obj) request = self.context.get('request', None) if 'theme_data' in data and data['theme_data'] is None: data.pop('theme_data') if ('request' in self.context and 'wrap_outgoing_links' in self.context['request'].GET): for key in ('homepage', 'support_url', 'contributions_url'): if key in data: data[key] = self.outgoingify(data[key]) if obj.type == amo.ADDON_PERSONA: if 'weekly_downloads' in data: # weekly_downloads don't make sense for lightweight themes. data.pop('weekly_downloads') if ('average_daily_users' in data and not self.is_broken_persona(obj)): # In addition, their average_daily_users number must come from # the popularity field of the attached Persona. data['average_daily_users'] = obj.persona.popularity if request and is_gate_active(request, 'del-addons-created-field'): data.pop('created', None) return data def outgoingify(self, data): if data: if isinstance(data, six.string_types): return get_outgoing_url(data) elif isinstance(data, dict): return {key: get_outgoing_url(value) if value else None for key, value in data.items()} # None or empty string... don't bother. return data def get_categories(self, obj): return { app_short_name: [cat.slug for cat in categories] for app_short_name, categories in obj.app_categories.items() } def get_has_eula(self, obj): return bool(getattr(obj, 'has_eula', obj.eula)) def get_is_featured(self, obj): # obj._is_featured is set from ES, so will only be present for list # requests. if not hasattr(obj, '_is_featured'): # Any featuring will do. obj._is_featured = obj.is_featured(app=None, lang=None) return obj._is_featured def get_has_privacy_policy(self, obj): return bool(getattr(obj, 'has_privacy_policy', obj.privacy_policy)) def get_tags(self, obj): if not hasattr(obj, 'tag_list'): attach_tags([obj]) # attach_tags() might not have attached anything to the addon, if it # had no tags. return getattr(obj, 'tag_list', []) def get_url(self, obj): # Use get_detail_url(), get_url_path() does an extra check on # current_version that is annoying in subclasses which don't want to # load that version. return absolutify(obj.get_detail_url()) def get_edit_url(self, obj): return absolutify(obj.get_dev_url()) def get_ratings_url(self, obj): return absolutify(obj.ratings_url) def get_review_url(self, obj): return absolutify(reverse('reviewers.review', args=[obj.pk])) def get_icon_url(self, obj): if self.is_broken_persona(obj): return absolutify(obj.get_default_icon_url(64)) return absolutify(obj.get_icon_url(64)) def get_icons(self, obj): if self.is_broken_persona(obj): get_icon = obj.get_default_icon_url else: get_icon = obj.get_icon_url return {str(size): absolutify(get_icon(size)) for size in amo.ADDON_ICON_SIZES} def get_ratings(self, obj): return { 'average': obj.average_rating, 'bayesian_average': obj.bayesian_rating, 'count': obj.total_ratings, 'text_count': obj.text_ratings_count, } def get_theme_data(self, obj): theme_data = None if obj.type == amo.ADDON_PERSONA and not self.is_broken_persona(obj): theme_data = obj.persona.theme_data return theme_data def is_broken_persona(self, obj): """Find out if the object is a Persona and either is missing its Persona instance or has a broken one. Call this everytime something in the serializer is suceptible to call something on the Persona instance, explicitly or not, to avoid 500 errors and/or SQL queries in ESAddonSerializer.""" try: # Setting obj.persona = None in ESAddonSerializer.fake_object() # below sadly isn't enough, so we work around it in that method by # creating a Persona instance with a custom '_broken' # attribute indicating that it should not be used. if obj.type == amo.ADDON_PERSONA and ( obj.persona is None or hasattr(obj.persona, '_broken')): raise Persona.DoesNotExist except Persona.DoesNotExist: # We got a DoesNotExist exception, therefore the Persona does not # exist or is broken. return True # Everything is fine, move on. return False