class AppSerializer(serializers.ModelSerializer): app_type = serializers.ChoiceField( choices=mkt.ADDON_WEBAPP_TYPES_LOOKUP.items(), read_only=True) author = serializers.CharField(source='developer_name', read_only=True) categories = ListField(serializers.ChoiceField(choices=CATEGORY_CHOICES), required=True) 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) feature_compatibility = serializers.SerializerMethodField( 'get_feature_compatibility') file_size = serializers.IntegerField(source='file_size', read_only=True) icons = serializers.SerializerMethodField('get_icons') id = serializers.IntegerField(source='pk', required=False) is_disabled = serializers.BooleanField(read_only=True, default=False) is_homescreen = serializers.SerializerMethodField('get_is_homescreen') is_offline = serializers.BooleanField(read_only=True) is_packaged = serializers.BooleanField(read_only=True) last_updated = serializers.DateField(read_only=True) manifest_url = serializers.CharField(source='get_manifest_url', read_only=True) modified = serializers.DateField(read_only=True) name = TranslationSerializerField(required=False) package_path = serializers.CharField(source='get_package_path', read_only=True) payment_account = serializers.SerializerMethodField('get_payment_account') payment_required = serializers.SerializerMethodField( 'get_payment_required') premium_type = ReverseChoiceField(choices_dict=mkt.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) promo_imgs = serializers.SerializerMethodField('get_promo_imgs') 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( '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') class Meta: model = Webapp fields = [ 'app_type', 'author', 'categories', 'content_ratings', 'created', 'current_version', 'default_locale', 'description', 'device_types', 'feature_compatibility', 'file_size', 'homepage', 'hosted_url', 'icons', 'id', 'is_disabled', 'is_homescreen', 'is_offline', 'is_packaged', 'last_updated', 'manifest_url', 'name', 'package_path', 'payment_account', 'payment_required', 'premium_type', 'previews', 'price', 'price_locale', 'privacy_policy', 'promo_imgs', 'public_stats', 'release_notes', 'ratings', 'regions', 'resource_uri', 'slug', 'status', 'support_email', 'support_url', 'supported_locales', 'tags', 'upsell', 'upsold', 'user', 'versions' ] 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.rating_descriptors.to_keys_by_body(body) if hasattr(app, 'rating_descriptors') else []), 'descriptors_text': ([ mkt.iarc_mappings.REVERSE_DESCS[key] for key in app.rating_descriptors.to_keys_by_body(body) ] if hasattr(app, 'rating_descriptors') else []), 'interactives': (app.rating_interactives.to_keys() if hasattr( app, 'rating_interactives') else []), 'interactives_text': ([ mkt.iarc_mappings.REVERSE_INTERACTIVES[key] for key in app.rating_interactives.to_keys() ] if hasattr(app, 'rating_interactives') else []), } def get_icons(self, app): return dict([(icon_size, app.get_icon_url(icon_size)) for icon_size in mkt.CONTENT_ICON_SIZES]) def get_feature_compatibility(self, app): request = self.context['request'] if not hasattr(request, 'feature_profile'): load_feature_profile(request) if request.feature_profile is None: # No profile information sent, we can't return compatibility, # return null. return None app_features = app.current_version.features.to_list() return request.feature_profile.has_features(app_features) 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(): return app.get_price(region=self._get_region_id()) return None def get_price_locale(self, app): if app.has_premium(): return app.get_price_locale(region=self._get_region_id()) return None 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 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): if not hasattr(app, 'tags_list'): attach_tags([app]) return getattr(app, 'tags_list', []) 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): request = self.context.get('request') if request and request.user.is_authenticated(): user = request.user return { 'developed': app.addonuser_set.filter(user=user, role=mkt.AUTHOR_ROLE_OWNER).exists(), 'installed': app.has_installed(user), 'purchased': app.pk in user.purchase_ids(), } def get_is_homescreen(self, app): return app.is_homescreen() 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 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 = mkt.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 = [mkt.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 mkt.WEBAPPS_APPROVED_STATUSES: mark_for_rereview(obj, added_devices, removed_devices) 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 mkt.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 (mkt.ADDON_FREE, mkt.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
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()