class WebsiteSerializer(serializers.ModelSerializer): categories = ListField(serializers.CharField()) description = TranslationSerializerField() device_types = ListField(serializers.CharField(), source='device_names') icons = serializers.SerializerMethodField('get_icons') id = serializers.IntegerField(source='pk') keywords = serializers.SerializerMethodField('get_keywords') name = TranslationSerializerField() promo_imgs = serializers.SerializerMethodField('get_promo_imgs') short_name = TranslationSerializerField() title = TranslationSerializerField() class Meta: model = Website fields = [ 'categories', 'description', 'device_types', 'icons', 'id', 'keywords', 'mobile_url', 'name', 'promo_imgs', 'short_name', 'title', 'url' ] def get_icons(self, obj): return { icon_size: obj.get_icon_url(icon_size) for icon_size in CONTENT_ICON_SIZES } def get_keywords(self, obj): if not hasattr(obj, 'keywords_list'): attach_tags([obj]) return getattr(obj, 'keywords_list', []) def get_promo_imgs(self, obj): return dict([(promo_img_size, obj.get_promo_img_url(promo_img_size)) for promo_img_size in mkt.PROMO_IMG_SIZES])
def test_field_null(self): field = TranslationSerializerField() app = Webapp() result = field.field_to_native(app, "name") eq_(result, None) result = field.field_to_native(app, "description") eq_(result, None)
def test_field_to_native_request_GET(self): request = Request(self.factory.get('/')) mock_serializer = Serializer() mock_serializer.context = {'request': request} field = TranslationSerializerField() field.initialize(mock_serializer, 'name') self._test_expected_dict(field)
def test_field_to_native_request_POST(self): request = Request(self.factory.post("/")) mock_serializer = Serializer() mock_serializer.context = {"request": request} field = TranslationSerializerField() field.initialize(mock_serializer, "name") self._test_expected_dict(field)
def test_field_to_native(self): class TestObject(object): test_field = u'Yes We Can' field = TranslationSerializerField() result = field.field_to_native(TestObject(), 'test_field') eq_(result, TestObject.test_field)
def test_field_null(self): field = TranslationSerializerField() app = Webapp() result = field.field_to_native(app, 'name') eq_(result, None) result = field.field_to_native(app, 'description') eq_(result, None)
def test_field_from_native_strip(self): data = { 'fr': u' Non mais Allô quoi ! ', 'en-US': u'' } field = TranslationSerializerField() result = field.from_native(data) eq_(result, {'fr': u'Non mais Allô quoi !', 'en-US': u''})
def test_field_to_native_source(self): self.app.mymock = Mock() self.app.mymock.mymocked_field = self.app.name field = TranslationSerializerField(source="mymock.mymocked_field") result = field.field_to_native(self.app, "shouldbeignored") expected = { "en-US": unicode(Translation.objects.get(id=self.app.name.id, locale="en-US")), "es": unicode(Translation.objects.get(id=self.app.name.id, locale="es")), } eq_(result, expected)
class CollectionSerializer(serializers.ModelSerializer): name = TranslationSerializerField() description = TranslationSerializerField() slug = serializers.CharField(required=False) collection_type = serializers.IntegerField() apps = CollectionMembershipField(many=True, source='collectionmembership_set') image = HyperlinkedRelatedOrNullField( source='*', view_name='collection-image-detail', predicate=lambda o: o.has_image) carrier = SlugChoiceField(required=False, choices_dict=mkt.carriers.CARRIER_MAP) region = SlugChoiceField(required=False, choices_dict=mkt.regions.REGIONS_DICT) category = SlugModelChoiceField(required=False, queryset=Category.objects.filter(type=amo.ADDON_WEBAPP)) class Meta: fields = ('apps', 'author', 'background_color', 'carrier', 'category', 'collection_type', 'default_language', 'description', 'id', 'image', 'is_public', 'name', 'region', 'slug', 'text_color',) model = Collection def full_clean(self, instance): instance = super(CollectionSerializer, self).full_clean(instance) if not instance: return None # For featured apps and operator shelf collections, we need to check if # one already exists for the same region/category/carrier combination. # # Sadly, this can't be expressed as a db-level unique constaint, # because this doesn't apply to basic collections. # # We have to do it ourselves, and we need the rest of the validation # to have already taken place, and have the incoming data and original # data from existing instance if it's an edit, so full_clean() is the # best place to do it. unique_collections_types = (COLLECTIONS_TYPE_FEATURED, COLLECTIONS_TYPE_OPERATOR) qs = Collection.objects.filter( collection_type=instance.collection_type, category=instance.category, region=instance.region, carrier=instance.carrier) if instance.pk: qs = qs.exclude(pk=instance.pk) if (instance.collection_type in unique_collections_types and qs.exists()): self._errors['collection_uniqueness'] = _( u'You can not have more than one Featured Apps/Operator Shelf ' u'collection for the same category/carrier/region combination.' ) return instance
def test_field_to_native_source(self): self.app.mymock = Mock() self.app.mymock.mymocked_field = self.app.name field = TranslationSerializerField(source='mymock.mymocked_field') result = field.field_to_native(self.app, 'shouldbeignored') expected = { 'en-US': unicode(Translation.objects.get(id=self.app.name.id, locale='en-US')), 'es': unicode(Translation.objects.get(id=self.app.name.id, locale='es')), } eq_(result, expected)
class FeedCollectionSerializer(BaseFeedCollectionSerializer): """ A serializer for the FeedCollection class. """ type = serializers.ChoiceField(choices=constants.COLLECTION_TYPE_CHOICES) background_image = FeedImageField( source='*', view_name='api-v2:feed-collection-image-detail', format='png') color = serializers.CharField(max_length=20, required=False) description = TranslationSerializerField(required=False) name = TranslationSerializerField() apps = serializers.SerializerMethodField('get_apps') # Deprecated. background_color = serializers.CharField(max_length=7, required=False) class Meta: fields = ('app_count', 'apps', 'background_color', 'background_image', 'color', 'description', 'id', 'name', 'slug', 'type', 'url') model = FeedCollection url_basename = 'feedcollections' def validate_color(self, attrs, source): color = attrs.get(source, None) if (attrs.get('type') == constants.COLLECTION_PROMO and not color): raise serializers.ValidationError( '`color` is required for `promo` collections.' ) if color and color not in dict(collection_colors.COLLECTION_COLORS): raise serializers.ValidationError( '`Not a valid value for `color`.' ) return attrs def get_apps(self, obj): """ Return a list of serialized apps, adding each app's `group` to the serialization. """ ret = [] memberships = FeedCollectionMembership.objects.filter(obj_id=obj.id) field = TranslationSerializerField() field.initialize(self, 'group') field.context = self.context for member in memberships: data = AppSerializer(member.app, context=self.context).data data['group'] = field.field_to_native(member, 'group') ret.append(data) return ret
def test_field_to_native(self): app = Webapp.objects.get(pk=337141) field = TranslationSerializerField() result = field.field_to_native(app, 'name') expected = { 'en-US': Translation.objects.get(id=app.name.id, locale='en-US'), 'es': Translation.objects.get(id=app.name.id, locale='es') } eq_(result, expected) result = field.field_to_native(app, 'description') expected = { 'en-US': Translation.objects.get(id=app.description.id, locale='en-US'), } eq_(result, expected)
def test_field_to_native_request_GET_lang(self): """ Pass a lang in the query string, expect to have a single string returned instead of an object. """ # Note that we don't go through the middlewares etc so the actual # language for the process isn't changed, we don't care as # _expect_single_string() method simply tests with the current language, # whatever it is. request = Request(self.factory.get('/', {'lang': 'lol'})) eq_(request.GET['lang'], 'lol') mock_serializer = Serializer() mock_serializer.context = {'request': request} field = TranslationSerializerField() field.initialize(mock_serializer, 'name') self._test_expected_single_string(field)
class FeedAppSerializer(URLSerializerMixin, serializers.ModelSerializer): app = SplitField(relations.PrimaryKeyRelatedField(required=True), AppSerializer()) description = TranslationSerializerField(required=False) preview = SplitField(relations.PrimaryKeyRelatedField(required=False), PreviewSerializer()) pullquote_attribution = TranslationSerializerField(required=False) pullquote_rating = serializers.IntegerField(required=False) pullquote_text = TranslationSerializerField(required=False) class Meta: fields = ('app', 'description', 'id', 'preview', 'pullquote_attribution', 'pullquote_rating', 'pullquote_text', 'url') model = FeedApp url_basename = 'feedapp'
def test_from_native(self): data = u'Translatiön' field = TranslationSerializerField() result = field.from_native(data) eq_(result, data) data = {'fr': u'Non mais Allô quoi !', 'en-US': u'No But Hello what!'} field = TranslationSerializerField() result = field.from_native(data) eq_(result, data) data = ['Bad Data'] field = TranslationSerializerField() result = field.from_native(data) eq_(result, unicode(data))
class FeedShelfSerializer(BaseFeedCollectionSerializer): """ A serializer for the FeedBrand class, a type of collection that allows editors to quickly create content without involving localizers. """ apps = serializers.SerializerMethodField('get_apps') background_image = FeedImageField( source='*', view_name='api-v2:feed-shelf-image-detail', format='png') background_image_landing = FeedLandingImageField( source='*', view_name='api-v2:feed-shelf-landing-image-detail', format='png') carrier = SlugChoiceField(choices_dict=mkt.carriers.CARRIER_MAP) description = TranslationSerializerField(required=False) is_published = serializers.BooleanField(source='is_published', required=False) name = TranslationSerializerField() region = SlugChoiceField(choices_dict=mkt.regions.REGION_LOOKUP) class Meta: fields = [ 'app_count', 'apps', 'background_image', 'background_image_landing', 'carrier', 'description', 'id', 'is_published', 'name', 'region', 'slug', 'url' ] model = FeedShelf url_basename = 'feedshelves' def get_apps(self, obj): """ Return a list of serialized apps, adding each app's `group` to the serialization. """ ret = [] memberships = FeedShelfMembership.objects.filter(obj_id=obj.id) field = TranslationSerializerField() field.initialize(self, 'group') field.context = self.context for member in memberships: data = AppSerializer(member.app, context=self.context).data data['group'] = field.field_to_native(member, 'group') ret.append(data) return ret
class FeedAppSerializer(URLSerializerMixin, serializers.ModelSerializer): """Thin wrappers around apps w/ metadata related to its feature in feed.""" app = SplitField(relations.PrimaryKeyRelatedField(required=True), AppSerializer()) description = TranslationSerializerField(required=False) image = CollectionImageField(source='*', view_name='feed-app-image-detail', format='png') preview = SplitField(relations.PrimaryKeyRelatedField(required=False), PreviewSerializer()) pullquote_attribution = TranslationSerializerField(required=False) pullquote_rating = serializers.IntegerField(required=False) pullquote_text = TranslationSerializerField(required=False) class Meta: fields = ('app', 'background_color', 'description', 'feedapp_type', 'id', 'image', 'preview', 'pullquote_attribution', 'pullquote_rating', 'pullquote_text', 'slug', 'url') model = FeedApp url_basename = 'feedapps'
class CollectionSerializer(serializers.ModelSerializer): name = TranslationSerializerField() description = TranslationSerializerField() slug = serializers.CharField(required=False) collection_type = serializers.IntegerField() apps = CollectionMembershipField(many=True, source='collectionmembership_set') class Meta: fields = ('apps', 'author', 'background_color', 'carrier', 'category', 'collection_type', 'default_language', 'description', 'id', 'is_public', 'name', 'region', 'slug', 'text_color',) model = Collection def full_clean(self, instance): instance = super(CollectionSerializer, self).full_clean(instance) if not instance: return None # For featured apps and operator shelf collections, we need to check if # one already exists for the same region/category/carrier combination. # # Sadly, this can't be expressed as a db-level unique constaint, # because this doesn't apply to basic collections. # # We have to do it ourselves, and we need the rest of the validation # to have already taken place, and have the incoming data and original # data from existing instance if it's an edit, so full_clean() is the # best place to do it. unique_collections_types = (COLLECTIONS_TYPE_FEATURED, COLLECTIONS_TYPE_OPERATOR) if (instance.collection_type in unique_collections_types and Collection.objects.filter(collection_type=instance.collection_type, category=instance.category, region=instance.region, carrier=instance.carrier).exists()): self._errors['collection_uniqueness'] = _( u'You can not have more than one Featured Apps/Operator Shelf ' u'collection for the same category/carrier/region combination.' ) return instance
class ExtensionSerializer(ModelSerializer): description = TranslationSerializerField(read_only=True) device_types = ListField(CharField(), source='device_names') icons = SerializerMethodField('get_icons') latest_public_version = ExtensionVersionSerializer( source='latest_public_version', read_only=True) latest_version = ExtensionVersionSerializer(source='latest_version', read_only=True) mini_manifest_url = CharField(source='mini_manifest_url', read_only=True) name = TranslationSerializerField(read_only=True) status = ReverseChoiceField(choices_dict=STATUS_CHOICES_API_v2, read_only=True) # FIXME: latest_version potentially expose private data. # Nothing extremely major, but maybe we care. Not a fan of moving it to # another endpoint since that'd mean developers and reviewers would need # to use that other endpoint instead of the regular one, but maybe that's # the way to go ? That endpoint could include all versions info, too. def get_icons(self, obj): return {size: obj.get_icon_url(size) for size in (64, 128)} class Meta: model = Extension fields = [ 'id', 'author', 'description', 'device_types', 'disabled', 'icons', 'last_updated', 'latest_version', 'latest_public_version', 'mini_manifest_url', 'name', 'slug', 'status', 'uuid', ]
class FeedAppSerializer(ValidateSlugMixin, URLSerializerMixin, serializers.ModelSerializer): """ A serializer for the FeedApp class, which highlights a single app and some additional metadata (e.g. a review or a screenshot). """ app = SplitField( relations.PrimaryKeyRelatedField(required=True, queryset=Webapp.objects), AppSerializer()) background_image = FeedImageField(allow_null=True) description = TranslationSerializerField(required=False) preview = SplitField( relations.PrimaryKeyRelatedField(required=False, queryset=Preview.objects), FeedPreviewESSerializer()) pullquote_rating = serializers.IntegerField(required=False, max_value=5, min_value=1) pullquote_text = TranslationSerializerField(required=False) class Meta: fields = ('app', 'background_color', 'background_image', 'color', 'created', 'description', 'id', 'preview', 'pullquote_attribution', 'pullquote_rating', 'pullquote_text', 'slug', 'type', 'url') model = FeedApp url_basename = 'feedapps' def validate(self, attrs): """ Require `pullquote_text` if `pullquote_rating` or `pullquote_attribution` are set. """ if (not attrs.get('pullquote_text') and (attrs.get('pullquote_rating') or attrs.get('pullquote_attribution'))): raise ValidationError('Pullquote text required if rating or ' 'attribution is defined.') return attrs
class FeedAppSerializer(URLSerializerMixin, serializers.ModelSerializer): """ A serializer for the FeedApp class, which highlights a single app and some additional metadata (e.g. a review or a screenshot). """ app = SplitField(relations.PrimaryKeyRelatedField(required=True), AppSerializer()) description = TranslationSerializerField(required=False) background_image = CollectionImageField( source='*', view_name='api-v2:feed-app-image-detail', format='png') preview = SplitField(relations.PrimaryKeyRelatedField(required=False), PreviewSerializer()) pullquote_rating = serializers.IntegerField(required=False) pullquote_text = TranslationSerializerField(required=False) class Meta: fields = ('app', 'background_color', 'created', 'description', 'feedapp_type', 'id', 'background_image', 'preview', 'pullquote_attribution', 'pullquote_rating', 'pullquote_text', 'slug', 'url') model = FeedApp url_basename = 'feedapps'
class FeedShelfSerializer(BaseFeedCollectionSerializer): """ A serializer for the FeedBrand class, a type of collection that allows editors to quickly create content without involving localizers. """ background_image = FeedImageField( source='*', view_name='api-v2:feed-shelf-image-detail', format='png') background_image_landing = FeedLandingImageField( source='*', view_name='api-v2:feed-shelf-landing-image-detail', format='png') carrier = SlugChoiceField(choices_dict=mkt.carriers.CARRIER_MAP) description = TranslationSerializerField(required=False) is_published = serializers.BooleanField(source='is_published', required=False) name = TranslationSerializerField() region = SlugChoiceField(choices_dict=mkt.regions.REGION_LOOKUP) class Meta: fields = ['app_count', 'apps', 'background_image', 'background_image_landing', 'carrier', 'description', 'id', 'is_published', 'name', 'region', 'slug', 'url'] model = FeedShelf url_basename = 'feedshelves'
def get_apps(self, obj): """ Return a list of serialized apps, adding each app's `group` to the serialization. """ ret = [] memberships = FeedShelfMembership.objects.filter(obj_id=obj.id) field = TranslationSerializerField() field.bind('group', self) field.context = self.context for member in memberships: data = AppSerializer(member.app, context=self.context).data data['group'] = field.to_representation( field.get_attribute(member)) ret.append(data) return ret
def get_apps(self, obj): """ Return a list of serialized apps, adding each app's `group` to the serialization. """ ret = [] memberships = FeedCollectionMembership.objects.filter(obj_id=obj.id) field = TranslationSerializerField() field.initialize(self, 'group') field.context = self.context for member in memberships: data = AppSerializer(member.app, context=self.context).data data['group'] = field.field_to_native(member, 'group') ret.append(data) return ret
def test_from_native(self): data = u"Translatiön" field = TranslationSerializerField() result = field.from_native(data) eq_(result, data) data = {"fr": u"Non mais Allô quoi !", "en-US": u"No But Hello what!"} field = TranslationSerializerField() result = field.from_native(data) eq_(result, data) data = ["Bad Data"] field = TranslationSerializerField() result = field.from_native(data) eq_(result, unicode(data))
def test_from_native(self): data = u'Translatiön' field = TranslationSerializerField() result = field.from_native(data) eq_(result, data) data = { 'fr': u'Non mais Allô quoi !', 'en-US': u'No But Hello what!' } field = TranslationSerializerField() result = field.from_native(data) eq_(result, data) data = ['Bad Data'] field = TranslationSerializerField() result = field.from_native(data) eq_(result, unicode(data))
class CollectionSerializer(serializers.ModelSerializer): name = TranslationSerializerField() description = TranslationSerializerField() slug = serializers.CharField(required=False) collection_type = serializers.IntegerField() apps = CollectionMembershipField(many=True, source='apps') image = HyperlinkedRelatedOrNullField( source='*', view_name='collection-image-detail', format='png', predicate=lambda o: o.has_image) carrier = SlugChoiceField(required=False, empty=None, choices_dict=mkt.carriers.CARRIER_MAP) region = SlugChoiceField(required=False, empty=None, choices_dict=mkt.regions.REGION_LOOKUP) category = SlugModelChoiceField(required=False, queryset=Category.objects.filter(type=amo.ADDON_WEBAPP)) class Meta: fields = ('apps', 'author', 'background_color', 'can_be_hero', 'carrier', 'category', 'collection_type', 'default_language', 'description', 'id', 'image', 'is_public', 'name', 'region', 'slug', 'text_color',) model = Collection def to_native(self, obj): """ Remove `can_be_hero` from the serialization if this is not an operator shelf. """ native = super(CollectionSerializer, self).to_native(obj) if native['collection_type'] != COLLECTIONS_TYPE_OPERATOR: del native['can_be_hero'] return native def validate(self, attrs): """ Prevent operator shelves from being associated with a category. """ existing = getattr(self, 'object') exc = 'Operator shelves may not be associated with a category.' if (not existing and attrs['collection_type'] == COLLECTIONS_TYPE_OPERATOR and attrs.get('category')): raise serializers.ValidationError(exc) elif existing: collection_type = attrs.get('collection_type', existing.collection_type) category = attrs.get('category', existing.category) if collection_type == COLLECTIONS_TYPE_OPERATOR and category: raise serializers.ValidationError(exc) return attrs def full_clean(self, instance): instance = super(CollectionSerializer, self).full_clean(instance) if not instance: return None # For featured apps and operator shelf collections, we need to check if # one already exists for the same region/category/carrier combination. # # Sadly, this can't be expressed as a db-level unique constaint, # because this doesn't apply to basic collections. # # We have to do it ourselves, and we need the rest of the validation # to have already taken place, and have the incoming data and original # data from existing instance if it's an edit, so full_clean() is the # best place to do it. unique_collections_types = (COLLECTIONS_TYPE_FEATURED, COLLECTIONS_TYPE_OPERATOR) qs = Collection.objects.filter( collection_type=instance.collection_type, category=instance.category, region=instance.region, carrier=instance.carrier) if instance.pk: qs = qs.exclude(pk=instance.pk) if (instance.collection_type in unique_collections_types and qs.exists()): self._errors['collection_uniqueness'] = _( u'You can not have more than one Featured Apps/Operator Shelf ' u'collection for the same category/carrier/region combination.' ) return instance
class AppSerializer(serializers.ModelSerializer): app_type = serializers.ChoiceField( choices=amo.ADDON_WEBAPP_TYPES_LOOKUP.items(), read_only=True) author = serializers.CharField(source='developer_name', read_only=True) banner_message = TranslationSerializerField(read_only=True, source='geodata.banner_message') banner_regions = serializers.Field(source='geodata.banner_regions_slugs') categories = serializers.SlugRelatedField(source='categories', many=True, slug_field='slug', required=True, queryset=Category.objects.filter(type=amo.ADDON_WEBAPP)) content_ratings = serializers.SerializerMethodField('get_content_ratings') created = serializers.DateField(read_only=True) current_version = serializers.CharField( source='current_version.version', read_only=True) default_locale = serializers.CharField(read_only=True) device_types = SemiSerializerMethodField('get_device_types') description = TranslationSerializerField(required=False) homepage = TranslationSerializerField(required=False) icons = serializers.SerializerMethodField('get_icons') id = serializers.IntegerField(source='pk', required=False) is_packaged = serializers.BooleanField(read_only=True) manifest_url = serializers.CharField(source='get_manifest_url', read_only=True) name = TranslationSerializerField(required=False) payment_account = serializers.SerializerMethodField('get_payment_account') payment_required = serializers.SerializerMethodField( 'get_payment_required') premium_type = ReverseChoiceField( choices_dict=amo.ADDON_PREMIUM_API, required=False) previews = PreviewSerializer(many=True, required=False, source='all_previews') price = SemiSerializerMethodField('get_price') price_locale = serializers.SerializerMethodField('get_price_locale') privacy_policy = LargeTextField(view_name='app-privacy-policy-detail', required=False) public_stats = serializers.BooleanField(read_only=True) ratings = serializers.SerializerMethodField('get_ratings_aggregates') regions = RegionSerializer(read_only=True, source='get_regions') release_notes = TranslationSerializerField(read_only=True, source='current_version.releasenotes') resource_uri = serializers.HyperlinkedIdentityField(view_name='app-detail') slug = serializers.CharField(source='app_slug', required=False) status = serializers.IntegerField(read_only=True) support_email = TranslationSerializerField(required=False) support_url = TranslationSerializerField(required=False) supported_locales = serializers.SerializerMethodField( 'get_supported_locales') tags = serializers.SerializerMethodField('get_tags') upsell = serializers.SerializerMethodField('get_upsell') upsold = serializers.HyperlinkedRelatedField( view_name='app-detail', source='upsold.free', required=False, queryset=Webapp.objects.all()) user = serializers.SerializerMethodField('get_user_info') versions = serializers.SerializerMethodField('get_versions') weekly_downloads = serializers.SerializerMethodField( 'get_weekly_downloads') class Meta: model = Webapp fields = [ 'app_type', 'author', 'banner_message', 'banner_regions', 'categories', 'content_ratings', 'created', 'current_version', 'default_locale', 'description', 'device_types', 'homepage', 'icons', 'id', 'is_packaged', 'manifest_url', 'name', 'payment_account', 'payment_required', 'premium_type', 'previews', 'price', 'price_locale', 'privacy_policy', 'public_stats', 'release_notes', 'ratings', 'regions', 'resource_uri', 'slug', 'status', 'support_email', 'support_url', 'supported_locales', 'tags', 'upsell', 'upsold', 'user', 'versions', 'weekly_downloads'] def _get_region_id(self): request = self.context.get('request') REGION = getattr(request, 'REGION', None) return REGION.id if REGION else None def _get_region_slug(self): request = self.context.get('request') REGION = getattr(request, 'REGION', None) return REGION.slug if REGION else None def get_content_ratings(self, app): body = mkt.regions.REGION_TO_RATINGS_BODY().get( self._get_region_slug(), 'generic') return { 'body': body, 'rating': app.get_content_ratings_by_body().get(body, None), 'descriptors': app.get_descriptors_dehydrated().get(body, []), 'interactives': app.get_interactives_dehydrated(), } def get_icons(self, app): return dict([(icon_size, app.get_icon_url(icon_size)) for icon_size in (16, 48, 64, 128)]) def get_payment_account(self, app): # Avoid a query for payment_account if the app is not premium. if not app.is_premium(): return None try: # This is a soon to be deprecated API property that only # returns the Bango account for historic compatibility. app_acct = app.payment_account(PROVIDER_BANGO) return reverse('payment-account-detail', args=[app_acct.payment_account.pk]) except app.PayAccountDoesNotExist: return None def get_payment_required(self, app): if app.has_premium(): tier = app.get_tier() return bool(tier and tier.price) return False def get_price(self, app): if app.has_premium(): region = self._get_region_id() if region in app.get_price_region_ids(): return app.get_price(region=region) return None def get_price_locale(self, app): if app.has_premium(): region = self._get_region_id() if region in app.get_price_region_ids(): return app.get_price_locale(region=region) return None def get_ratings_aggregates(self, app): return {'average': app.average_rating, 'count': app.total_reviews} def get_supported_locales(self, app): locs = getattr(app.current_version, 'supported_locales', '') if locs: return locs.split(',') if isinstance(locs, basestring) else locs else: return [] def get_tags(self, app): return [t.tag_text for t in app.tags.all()] def get_upsell(self, app): upsell = False if app.upsell: upsell = app.upsell.premium # Only return the upsell app if it's public and we are not in an # excluded region. if (upsell and upsell.is_public() and self._get_region_id() not in upsell.get_excluded_region_ids()): return { 'id': upsell.id, 'app_slug': upsell.app_slug, 'icon_url': upsell.get_icon_url(128), 'name': unicode(upsell.name), 'resource_uri': reverse('app-detail', kwargs={'pk': upsell.pk}) } else: return False def get_user_info(self, app): user = getattr(self.context.get('request'), 'amo_user', None) if user: return { 'developed': app.addonuser_set.filter( user=user, role=amo.AUTHOR_ROLE_OWNER).exists(), 'installed': app.has_installed(user), 'purchased': app.pk in user.purchase_ids(), } def get_versions(self, app): # Disable transforms, we only need two fields: version and pk. # Unfortunately, cache-machine gets in the way so we can't use .only() # (.no_transforms() is ignored, defeating the purpose), and we can't use # .values() / .values_list() because those aren't cached :( return dict((v.version, reverse('version-detail', kwargs={'pk': v.pk})) for v in app.versions.all().no_transforms()) def get_weekly_downloads(self, app): if app.public_stats: return app.weekly_downloads def validate_categories(self, attrs, source): if not attrs.get('categories'): raise serializers.ValidationError('This field is required.') set_categories = set(attrs[source]) total = len(set_categories) max_cat = amo.MAX_CATEGORIES if total > max_cat: # L10n: {0} is the number of categories. raise serializers.ValidationError(ngettext( 'You can have only {0} category.', 'You can have only {0} categories.', max_cat).format(max_cat)) return attrs def get_device_types(self, app): with no_translation(): return [n.api_name for n in app.device_types] def save_device_types(self, obj, new_types): new_types = [amo.DEVICE_LOOKUP[d].id for d in new_types] old_types = [x.id for x in obj.device_types] added_devices = set(new_types) - set(old_types) removed_devices = set(old_types) - set(new_types) for d in added_devices: obj.addondevicetype_set.create(device_type=d) for d in removed_devices: obj.addondevicetype_set.filter(device_type=d).delete() # Send app to re-review queue if public and new devices are added. if added_devices and obj.status in amo.WEBAPPS_APPROVED_STATUSES: mark_for_rereview(obj, added_devices, removed_devices) def save_categories(self, obj, categories): before = set(obj.categories.values_list('id', flat=True)) # Add new categories. to_add = set(c.id for c in categories) - before for c in to_add: AddonCategory.objects.create(addon=obj, category_id=c) # Remove old categories. to_remove = before - set(categories) for c in to_remove: obj.addoncategory_set.filter(category=c).delete() def save_upsold(self, obj, upsold): current_upsell = obj.upsold if upsold and upsold != obj.upsold.free: if not current_upsell: log.debug('[1@%s] Creating app upsell' % obj.pk) current_upsell = AddonUpsell(premium=obj) current_upsell.free = upsold current_upsell.save() elif current_upsell: # We're deleting the upsell. log.debug('[1@%s] Deleting the app upsell' % obj.pk) current_upsell.delete() def save_price(self, obj, price): premium = obj.premium if not premium: premium = AddonPremium() premium.addon = obj premium.price = Price.objects.active().get(price=price) premium.save() def validate_device_types(self, attrs, source): if attrs.get('device_types') is None: raise serializers.ValidationError('This field is required.') for v in attrs['device_types']: if v not in amo.DEVICE_LOOKUP.keys(): raise serializers.ValidationError( str(v) + ' is not one of the available choices.') return attrs def validate_price(self, attrs, source): if attrs.get('premium_type', None) not in (amo.ADDON_FREE, amo.ADDON_FREE_INAPP): valid_prices = Price.objects.exclude( price='0.00').values_list('price', flat=True) price = attrs.get('price') if not (price and Decimal(price) in valid_prices): raise serializers.ValidationError( 'Premium app specified without a valid price. Price can be' ' one of %s.' % (', '.join('"%s"' % str(p) for p in valid_prices),)) return attrs def restore_object(self, attrs, instance=None): # restore_object creates or updates a model instance, during # input validation. extras = [] # Upsell bits are handled here because we need to remove it # from the attrs dict before deserializing. upsold = attrs.pop('upsold.free', None) if upsold is not None: extras.append((self.save_upsold, upsold)) price = attrs.pop('price', None) if price is not None: extras.append((self.save_price, price)) device_types = attrs['device_types'] if device_types: extras.append((self.save_device_types, device_types)) del attrs['device_types'] instance = super(AppSerializer, self).restore_object( attrs, instance=instance) for f, v in extras: f(instance, v) return instance def save_object(self, obj, **kwargs): # this only gets called if validation succeeds. m2m = getattr(obj, '_m2m_data', {}) cats = m2m.pop('categories', None) super(AppSerializer, self).save_object(obj, **kwargs) # Categories are handled here because we can't look up # existing ones until the initial save is done. self.save_categories(obj, cats)
class CannedResponseSerializer(serializers.ModelSerializer): name = TranslationSerializerField(required=True) response = TranslationSerializerField(required=True) class Meta: model = CannedResponse
class AppSerializer(serializers.ModelSerializer): app_type = serializers.ChoiceField( choices=amo.ADDON_WEBAPP_TYPES_LOOKUP.items(), read_only=True) author = serializers.CharField(source='developer_name', read_only=True) categories = serializers.SlugRelatedField( many=True, slug_field='slug', required=True, queryset=Category.objects.filter(type=amo.ADDON_WEBAPP)) content_ratings = serializers.SerializerMethodField('get_content_ratings') created = serializers.DateField(read_only=True) current_version = serializers.CharField(source='current_version.version', read_only=True) default_locale = serializers.CharField(read_only=True) device_types = SemiSerializerMethodField('get_device_types') description = TranslationSerializerField(required=False) homepage = TranslationSerializerField(required=False) icons = serializers.SerializerMethodField('get_icons') id = serializers.IntegerField(source='pk', required=False) is_packaged = serializers.BooleanField(read_only=True) manifest_url = serializers.CharField(source='get_manifest_url', read_only=True) name = TranslationSerializerField(required=False) payment_account = serializers.HyperlinkedRelatedField( view_name='payment-account-detail', source='app_payment_account', required=False) payment_required = serializers.SerializerMethodField( 'get_payment_required') premium_type = ReverseChoiceField(choices_dict=amo.ADDON_PREMIUM_API, required=False) previews = PreviewSerializer(many=True, required=False) price = SemiSerializerMethodField('get_price') price_locale = serializers.SerializerMethodField('get_price_locale') privacy_policy = LargeTextField(view_name='app-privacy-policy-detail', required=False) public_stats = serializers.BooleanField(read_only=True) ratings = serializers.SerializerMethodField('get_ratings_aggregates') regions = RegionSerializer(read_only=True, source='get_regions') resource_uri = serializers.HyperlinkedIdentityField(view_name='app-detail') slug = serializers.CharField(source='app_slug', required=False) status = serializers.IntegerField(read_only=True) summary = TranslationSerializerField(required=False) support_email = TranslationSerializerField(required=False) support_url = TranslationSerializerField(required=False) supported_locales = serializers.SerializerMethodField( 'get_supported_locales') tags = serializers.SerializerMethodField('get_tags') upsell = serializers.SerializerMethodField('get_upsell') upsold = serializers.HyperlinkedRelatedField(view_name='app-detail', source='upsold.free', required=False, queryset=Webapp.objects.all()) user = serializers.SerializerMethodField('get_user_info') versions = serializers.SerializerMethodField('get_versions') weekly_downloads = serializers.SerializerMethodField( 'get_weekly_downloads') class Meta: model = Webapp exclude = [ '_latest_version', 'ts_slowness', '_backup_version', 'last_updated', 'nomination_message', '_current_version', 'make_public', 'charity', 'modified' ] def get_content_ratings(self, app): return { 'ratings': app.get_content_ratings_by_region() or None, 'descriptors': app.get_descriptors() or None, 'interactive_elements': app.get_interactives() or None, } def get_icons(self, app): return dict([(icon_size, app.get_icon_url(icon_size)) for icon_size in (16, 48, 64, 128)]) def get_payment_required(self, app): if app.premium: tier = app.get_tier() return bool(tier and tier.price) return False def get_price(self, app): region = self.context.get('region') if (region in app.get_price_region_ids() or payments_enabled(self.context['request'])): return app.get_price(region=region) def get_price_locale(self, app): region = self.context.get('region') if (region in app.get_price_region_ids() or payments_enabled(self.context['request'])): return app.get_price_locale(region=region) def get_ratings_aggregates(self, app): return {'average': app.average_rating, 'count': app.total_reviews} def get_supported_locales(self, app): locs = getattr(app.current_version, 'supported_locales', '') if locs: return locs.split(',') else: return [] def get_tags(self, app): return [t.tag_text for t in app.tags.all()] def get_upsell(self, app): if (app.upsell and self.context.get('region') in app.upsell.premium.get_price_region_ids()): upsell = app.upsell.premium return { 'id': upsell.id, 'app_slug': upsell.app_slug, 'icon_url': upsell.get_icon_url(128), 'name': unicode(upsell.name), 'resource_uri': reverse('app-detail', kwargs={'pk': upsell.pk}) } else: return False def get_user_info(self, app): user = self.context.get('profile') if user: return { 'developed': app.addonuser_set.filter(user=user, role=amo.AUTHOR_ROLE_OWNER).exists(), 'installed': app.has_installed(user), 'purchased': app.pk in user.purchase_ids(), } def get_versions(self, app): return dict( (v.version, reverse_version(v)) for v in app.versions.all()) def get_weekly_downloads(self, app): if app.public_stats: return app.weekly_downloads def validate_categories(self, attrs, source): if not attrs.get('categories'): raise serializers.ValidationError('This field is required.') set_categories = set(attrs[source]) total = len(set_categories) max_cat = amo.MAX_CATEGORIES if total > max_cat: # L10n: {0} is the number of categories. raise serializers.ValidationError( ngettext('You can have only {0} category.', 'You can have only {0} categories.', max_cat).format(max_cat)) return attrs def get_device_types(self, app): with no_translation(): return [n.api_name for n in app.device_types] def save_device_types(self, obj, new_types): new_types = [amo.DEVICE_LOOKUP[d].id for d in new_types] old_types = [x.id for x in obj.device_types] added_devices = set(new_types) - set(old_types) removed_devices = set(old_types) - set(new_types) for d in added_devices: obj.addondevicetype_set.create(device_type=d) for d in removed_devices: obj.addondevicetype_set.filter(device_type=d).delete() # Send app to re-review queue if public and new devices are added. if added_devices and obj.status in amo.WEBAPPS_APPROVED_STATUSES: mark_for_rereview(obj, added_devices, removed_devices) def save_categories(self, obj, categories): before = set(obj.categories.values_list('id', flat=True)) # Add new categories. to_add = set(c.id for c in categories) - before for c in to_add: AddonCategory.objects.create(addon=obj, category_id=c) # Remove old categories. to_remove = before - set(categories) for c in to_remove: obj.addoncategory_set.filter(category=c).delete() # Disallow games in Brazil without a rating. games = Webapp.category('games') if not games: return for region in ALL_REGIONS_WITH_CONTENT_RATINGS: if (self.product.listed_in(region) and not self.product.content_ratings_in(region)): if games.id in to_add: aer, created = AddonExcludedRegion.objects.get_or_create( addon=self.product, region=region.id) if created: log.info(u'[Webapp:%s] Game excluded from new region ' u'(%s).' % (self.product, region.slug)) elif games.id in to_remove: self.product.addonexcludedregion.filter( region=region.id).delete() log.info(u'[Webapp:%s] Game no longer excluded from region' u' (%s).' % (self.product, region.slug)) def save_upsold(self, obj, upsold): current_upsell = obj.upsold if upsold and upsold != obj.upsold.free: if not current_upsell: log.debug('[1@%s] Creating app upsell' % obj.pk) current_upsell = AddonUpsell(premium=obj) current_upsell.free = upsold current_upsell.save() elif current_upsell: # We're deleting the upsell. log.debug('[1@%s] Deleting the app upsell' % obj.pk) current_upsell.delete() def save_price(self, obj, price): premium = obj.premium if not premium: premium = AddonPremium() premium.addon = obj premium.price = Price.objects.active().get(price=price) premium.save() def validate_device_types(self, attrs, source): if attrs.get('device_types') is None: raise serializers.ValidationError('This field is required.') for v in attrs['device_types']: if v not in amo.DEVICE_LOOKUP.keys(): raise serializers.ValidationError( str(v) + ' is not one of the available choices.') return attrs def validate_price(self, attrs, source): if attrs.get('premium_type', None) not in (amo.ADDON_FREE, amo.ADDON_FREE_INAPP): valid_prices = Price.objects.exclude(price='0.00').values_list( 'price', flat=True) price = attrs.get('price') if not (price and Decimal(price) in valid_prices): raise serializers.ValidationError( 'Premium app specified without a valid price. Price can be' ' one of %s.' % (', '.join('"%s"' % str(p) for p in valid_prices), )) return attrs def restore_object(self, attrs, instance=None): # restore_object creates or updates a model instance, during # input validation. extras = [] # Upsell bits are handled here because we need to remove it # from the attrs dict before deserializing. upsold = attrs.pop('upsold.free', None) if upsold is not None: extras.append((self.save_upsold, upsold)) price = attrs.pop('price', None) if price is not None: extras.append((self.save_price, price)) device_types = attrs['device_types'] if device_types: extras.append((self.save_device_types, device_types)) del attrs['device_types'] if attrs.get('app_payment_account') is None: attrs.pop('app_payment_account') instance = super(AppSerializer, self).restore_object(attrs, instance=instance) for f, v in extras: f(instance, v) return instance def save_object(self, obj, **kwargs): # this only gets called if validation succeeds. m2m = getattr(obj, '_m2m_data', {}) cats = m2m.pop('categories', None) super(AppSerializer, self).save_object(obj, **kwargs) # Categories are handled here because we can't look up # existing ones until the initial save is done. self.save_categories(obj, cats)
class BaseAppSerializer(serializers.ModelSerializer): # REST Framework 3.x doesn't allow meta.fields to omit fields declared in # the class body, but it does allow omitting ones in superclasses. All the # serializers are subsets of the full field collection, hence this # superclass. app_type = serializers.ChoiceField( choices=mkt.ADDON_WEBAPP_TYPES_LOOKUP.items(), read_only=True) author = serializers.CharField(source='developer_name', read_only=True) categories = serializers.ListField(child=serializers.ChoiceField( choices=CATEGORY_CHOICES, read_only=False), read_only=False, required=True) content_ratings = serializers.SerializerMethodField() created = serializers.DateTimeField(read_only=True, format=None) current_version = serializers.CharField(source='current_version.version', read_only=True) default_locale = serializers.CharField(read_only=True) device_types = SemiSerializerMethodField() description = TranslationSerializerField(required=False) homepage = TranslationSerializerField(required=False) feature_compatibility = serializers.SerializerMethodField() file_size = serializers.IntegerField(read_only=True) icons = serializers.SerializerMethodField() id = serializers.IntegerField(source='pk', required=False) is_disabled = serializers.BooleanField(read_only=True) is_homescreen = serializers.SerializerMethodField() is_offline = serializers.BooleanField(read_only=True) is_packaged = serializers.BooleanField(read_only=True) last_updated = serializers.DateTimeField(read_only=True, format=None) manifest_url = serializers.CharField(source='get_manifest_url', read_only=True) modified = serializers.DateTimeField(read_only=True, format=None) name = TranslationSerializerField(required=False) package_path = serializers.CharField(source='get_package_path', read_only=True) payment_account = serializers.SerializerMethodField() payment_required = serializers.SerializerMethodField() premium_type = ReverseChoiceField(choices_dict=mkt.ADDON_PREMIUM_API, required=False) previews = PreviewSerializer(many=True, required=False, source='all_previews') price = SemiSerializerMethodField(source='*', required=False) price_locale = serializers.SerializerMethodField() privacy_policy = LargeTextField(view_name='app-privacy-policy-detail', queryset=Webapp.objects, required=False) promo_imgs = serializers.SerializerMethodField() public_stats = serializers.BooleanField(read_only=True) ratings = serializers.SerializerMethodField('get_ratings_aggregates') regions = RegionSerializer(read_only=True, source='get_regions', many=True) release_notes = TranslationSerializerField( read_only=True, source='current_version.releasenotes') resource_uri = serializers.HyperlinkedIdentityField(view_name='app-detail') slug = serializers.CharField(source='app_slug', required=False) status = serializers.IntegerField(read_only=True) support_email = TranslationSerializerField(required=False) support_url = TranslationSerializerField(required=False) supported_locales = serializers.SerializerMethodField() tags = serializers.SerializerMethodField() upsell = serializers.SerializerMethodField() upsold = serializers.HyperlinkedRelatedField(view_name='app-detail', source='upsold.free', required=False, queryset=Webapp.objects.all()) user = serializers.SerializerMethodField('get_user_info') versions = serializers.SerializerMethodField()
def test_field_to_native_empty_context(self): mock_serializer = Serializer() mock_serializer.context = {} field = TranslationSerializerField() field.initialize(mock_serializer, 'name') self._test_expected_dict(field)
def test_field_from_native_strip(self): data = {"fr": u" Non mais Allô quoi ! ", "en-US": u""} field = TranslationSerializerField() result = field.from_native(data) eq_(result, {"fr": u"Non mais Allô quoi !", "en-US": u""})
def test_field_to_native(self): field = TranslationSerializerField() self._test_expected_dict(field)
def test_field_from_native_strip(self): data = {'fr': u' Non mais Allô quoi ! ', 'en-US': u''} field = TranslationSerializerField() result = field.from_native(data) eq_(result, {'fr': u'Non mais Allô quoi !', 'en-US': u''})