class FileStatusSerializer(serializers.ModelSerializer): status = ReverseChoiceField(choices_dict=mkt.STATUS_CHOICES_API, required=True) class Meta: model = File fields = ('status', )
class ExtensionVersionSerializer(ModelSerializer): download_url = CharField(read_only=True) unsigned_download_url = CharField(read_only=True) reviewer_mini_manifest_url = CharField(read_only=True) status = ReverseChoiceField(choices_dict=STATUS_FILE_CHOICES_API_v2, read_only=True) class Meta: model = ExtensionVersion fields = [ 'id', 'created', 'download_url', 'reviewer_mini_manifest_url', 'unsigned_download_url', 'size', 'status', 'version' ]
class AppStatusSerializer(serializers.ModelSerializer): status = ReverseChoiceField(choices_dict=amo.STATUS_CHOICES_API, required=False) disabled_by_user = serializers.BooleanField(required=False) allowed_statuses = { # You can push to the pending queue. amo.STATUS_NULL: [amo.STATUS_PENDING], # Approved apps can be public or unlisted. amo.STATUS_APPROVED: [amo.STATUS_PUBLIC, amo.STATUS_UNLISTED], # Public apps can choose to become private (APPROVED) or unlisted. amo.STATUS_PUBLIC: [amo.STATUS_APPROVED, amo.STATUS_UNLISTED], # Unlisted apps can become private or public. amo.STATUS_UNLISTED: [amo.STATUS_APPROVED, amo.STATUS_PUBLIC], } class Meta: model = Webapp fields = ('status', 'disabled_by_user') def validate_status(self, attrs, source): if not self.object: raise serializers.ValidationError(u'Error getting app.') if source not in attrs: return attrs # Admins can change any status, skip validation for them. # It's dangerous, but with great powers comes great responsability. if ('request' in self.context and self.context['request'].user and acl.action_allowed(self.context['request'], 'Admin', '%')): return attrs # An incomplete app's status can not be changed. if not self.object.is_fully_complete(): raise serializers.ValidationError( self.object.completion_error_msgs()) # Only some specific changes are possible depending on the app current # status. if (self.object.status not in self.allowed_statuses or attrs[source] not in self.allowed_statuses[self.object.status]): raise serializers.ValidationError( 'App status can not be changed to the one you specified.') return attrs
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 AppStatusSerializer(serializers.ModelSerializer): status = ReverseChoiceField(choices_dict=amo.STATUS_CHOICES_API, required=False) disabled_by_user = serializers.BooleanField(required=False) allowed_statuses = { # You can push to the pending queue. amo.STATUS_NULL: amo.STATUS_PENDING, # You can push to public if you've been reviewed. amo.STATUS_PUBLIC_WAITING: amo.STATUS_PUBLIC, } class Meta: model = Webapp fields = ('status', 'disabled_by_user') def validate_status(self, attrs, source): if not self.object: raise serializers.ValidationError(u'Error getting app.') if not source in attrs: return attrs # An incomplete app's status can not be changed. if not self.object.is_fully_complete(): raise serializers.ValidationError( self.object.completion_error_msgs()) # Only some specific changes are possible depending on the app current # status. if (self.object.status not in self.allowed_statuses or attrs[source] != self.allowed_statuses[self.object.status]): raise serializers.ValidationError( 'App status can not be changed to the one you specified.') return attrs
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 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()
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)