class LicenseSerializer(serializers.ModelSerializer): is_custom = serializers.SerializerMethodField() name = serializers.SerializerMethodField() text = TranslationSerializerField() url = serializers.SerializerMethodField() class Meta: model = License fields = ('id', 'is_custom', 'name', 'text', 'url') def __init__(self, *args, **kwargs): super(LicenseSerializer, self).__init__(*args, **kwargs) self.db_name = TranslationSerializerField() self.db_name.bind('name', self) def get_is_custom(self, obj): return not bool(obj.builtin) def get_url(self, obj): return obj.url or self.get_version_license_url(obj) def get_version_license_url(self, obj): # We need the version associated with the license, because that's where # the license_url() method lives. The problem is, normally we would not # be able to do that, because there can be multiple versions for a # given License. However, since we're serializing through a nested # serializer, we cheat and use `instance.version_instance` which is # set by SimpleVersionSerializer.to_representation() while serializing. # Only get the version license url for non-builtin licenses. if not obj.builtin and hasattr(obj, 'version_instance'): return absolutify(obj.version_instance.license_url()) return None def get_name(self, obj): # See if there is a license constant license_constant = obj._constant if not license_constant: # If not fall back on the name in the database. return self.db_name.get_attribute(obj) else: request = self.context.get('request', None) if request and request.method == 'GET' and 'lang' in request.GET: # A single lang requested so return a flat string return six.text_type(license_constant.name) else: # Otherwise mock the dict with the default lang. lang = getattr(request, 'LANG', None) or settings.LANGUAGE_CODE return {lang: six.text_type(license_constant.name)} def to_representation(self, instance): data = super(LicenseSerializer, self).to_representation(instance) request = self.context.get('request', None) if request and is_gate_active( request, 'del-version-license-is-custom'): data.pop('is_custom', None) return data
class PreviewSerializer(serializers.ModelSerializer): caption = TranslationSerializerField() image_url = serializers.SerializerMethodField() thumbnail_url = serializers.SerializerMethodField() image_size = serializers.ReadOnlyField(source='image_dimensions') thumbnail_size = serializers.ReadOnlyField(source='thumbnail_dimensions') class Meta: # Note: this serializer can also be used for VersionPreview. model = Preview fields = ( 'id', 'caption', 'image_size', 'image_url', 'thumbnail_size', 'thumbnail_url', ) def get_image_url(self, obj): return absolutify(obj.image_url) def get_thumbnail_url(self, obj): return absolutify(obj.thumbnail_url)
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 CollectionSerializer(serializers.ModelSerializer): name = TranslationSerializerField() description = TranslationSerializerField(required=False) url = serializers.SerializerMethodField() author = BaseUserSerializer(default=serializers.CurrentUserDefault()) public = serializers.BooleanField(source='listed', default=True) uuid = serializers.UUIDField(format='hex', required=False) class Meta: model = Collection fields = ( 'id', 'uuid', 'url', 'addon_count', 'author', 'description', 'modified', 'name', 'slug', 'public', 'default_locale', ) writeable_fields = ('description', 'name', 'slug', 'public', 'default_locale') read_only_fields = tuple(set(fields) - set(writeable_fields)) validators = [ UniqueTogetherValidator( queryset=Collection.objects.all(), message=_('This custom URL is already in use by another one ' 'of your collections.'), fields=('slug', 'author'), ), ] def get_url(self, obj): return obj.get_abs_url() def validate_name(self, value): # if we have a localised dict of values validate them all. if isinstance(value, dict): return { locale: self.validate_name(sub_value) for locale, sub_value in value.items() } if value.strip() == '': raise serializers.ValidationError( ugettext('Name cannot be empty.')) if DeniedName.blocked(value): raise serializers.ValidationError( ugettext('This name cannot be used.')) return value def validate_description(self, value): if has_links(clean_nl(str(value))): # There's some links, we don't want them. raise serializers.ValidationError( ugettext('No links are allowed.')) return value def validate_slug(self, value): slug_validator( value, lower=False, message=ugettext('The custom URL must consist of letters, ' 'numbers, underscores or hyphens.'), ) if DeniedName.blocked(value): raise serializers.ValidationError( ugettext('This custom URL cannot be used.')) return value
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
def __init__(self, *args, **kwargs): super(LicenseSerializer, self).__init__(*args, **kwargs) self.db_name = TranslationSerializerField() self.db_name.bind('name', self)
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 = 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 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 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
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 = 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