Example #1
0
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()
Example #2
0
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,
        }
Example #3
0
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]))
Example #4
0
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='')
Example #5
0
class AddonFeatureCompatibilitySerializer(serializers.ModelSerializer):
    e10s = ReverseChoiceField(
        choices=amo.E10S_COMPATIBILITY_CHOICES_API.items())

    class Meta:
        model = AddonFeatureCompatibility
        fields = ('e10s', )
Example #6
0
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)
Example #7
0
class MinimalVersionSerializerWithChannel(MinimalVersionSerializer):
    channel = ReverseChoiceField(
        choices=list(amo.CHANNEL_CHOICES_API.items()))

    class Meta:
        model = Version
        fields = ('id', 'channel', 'version')
Example #8
0
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')
Example #10
0
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='')
Example #11
0
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
Example #12
0
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]
Example #13
0
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'
Example #14
0
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],
            ))
Example #15
0
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
Example #16
0
 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,
        }
Example #18
0
 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')
Example #19
0
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
Example #20
0
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
Example #21
0
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
Example #22
0
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)):
Example #23
0
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,
        }
Example #24
0
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
Example #25
0
 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'
Example #26
0
 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'
Example #27
0
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
Example #28
0
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
Example #29
0
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)):
Example #30
0
 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'
Example #31
0
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,
        }
Example #32
0
 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'
Example #33
0
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