class TwitterTailoredAudienceSerializer(serializers.ModelSerializer): tw_account_id = PKRelatedField(queryset=TwitterAccount.objects_raw.all()) name = serializers.CharField(required=True, allow_blank=False) audience_size = serializers.CharField(required=False, allow_blank=False) targetable_types = serializers.CharField( required=False, validators=[BaseValidator.JSONValidator], allow_blank=True) audience_type = serializers.CharField(required=False, allow_blank=False) targetable = serializers.NullBooleanField() list_type = serializers.CharField(required=False, allow_blank=False) reasons_not_targetable = serializers.CharField( required=False, validators=[BaseValidator.JSONValidator], allow_blank=True) partner_source = serializers.CharField(required=False, allow_blank=False) is_owner = serializers.NullBooleanField() permission_level = serializers.CharField(required=False, allow_blank=False) status = ChoiceCaseInsensitiveField(choices=STATUS_CHOICES, required=False) created_at = DateTimeField(read_only=True) last_update = DateTimeField(read_only=True) class Meta: model = TwitterTailoredAudience fields = ('tw_targeting_id', 'tw_account_id', 'name', 'audience_size', 'targetable_types', 'audience_type', 'targetable', 'list_type', 'reasons_not_targetable', 'partner_source', 'is_owner', 'permission_level', 'status', 'created_at', 'last_update') validators = [ serializers.UniqueTogetherValidator( queryset=TwitterTailoredAudience.objects_raw.all(), fields=('tw_targeting_id', )) ]
class AdvertiserSerializer(BaseModelSerializer): agency_id = PKRelatedField(queryset=Agency.objects_raw.all()) agency = serializers.CharField(read_only=True) trading_desk_id = serializers.IntegerField(read_only=True) trading_desk = serializers.CharField(read_only=True) advertiser_key = serializers.CharField(read_only=True) status = ChoiceCaseInsensitiveField(choices=STATUS_CHOICES, required=False) flight_start_date = ZeroDateField(required=False, allow_null=True) email = EmailField(max_length=255, required=False, allow_blank=True) contact = serializers.CharField(max_length=100, required=True) created_at = DateTimeField(read_only=True) last_update = DateTimeField(read_only=True) currency = ChoiceCaseInsensitiveField(choices=[ (x.currency_code, x.currency_code) for x in Currency.objects.all() ], allow_blank=True) required_in_schema = ['currency'] # pylint: disable=old-style-class class Meta: model = Advertiser fields = ('advertiser_id', 'advertiser', 'advertiser_key', 'agency_id', 'agency', 'trading_desk_id', 'trading_desk', 'account_manager', 'contact', 'address1', 'address2', 'city', 'state_prov', 'country', 'phone', 'email', 'notes', 'zip', 'sampling_rate', 'throttling_rate', 'flight_start_date', 'created_at', 'last_update', 'currency', 'twitter_margin', 'discount', 'status') validators = [ serializers.UniqueTogetherValidator( queryset=Advertiser.objects_raw.all(), fields=('agency_id', 'advertiser')) ] def is_valid(self, *args, **kwargs): errors = {} if not self.initial_data.get('agency_id', None): raise serializers.ValidationError( {'agency_id': 'agency_id field is required.'}) currency = self.initial_data.get('currency', None) twitter_margin = self.initial_data.get('twitter_margin', None) if self.instance and self.instance.currency and not currency: raise serializers.ValidationError( {'currency': 'agency_id field is required.'}) if int(str(twitter_margin)[::-1].find('.')) > 2: raise serializers.ValidationError( {'Twitter Service Fee': 'Incorrect value, must be integer.'}) if twitter_margin < 0: raise serializers.ValidationError({ 'Twitter Service Fee': 'Twitter Service Fee can not be lower than 0' }) return super(AdvertiserSerializer, self).is_valid(*args, **kwargs)
class DiscretePricingSerializer(BaseBidderListSerializer): created_at = DateTimeField() last_update = DateTimeField() tag = serializers.CharField() rev_value = serializers.DecimalField(max_digits=9, decimal_places=2) # pylint: disable=old-style-class class Meta: model = DiscretePricing
class TwitterCampaignSerializer(BaseModelSerializer): tw_account_id = PKRelatedField(queryset=TwitterAccount.objects_raw.all()) campaign_id = PKRelatedField(queryset=Campaign.objects_raw.all()) name = serializers.CharField(required=True, allow_blank=False) funding_instrument_id = serializers.CharField(required=True, allow_blank=True) start_time = ZeroDateTimeField(required=False, allow_null=True) end_time = ZeroDateTimeField(required=False, allow_null=True) standard_delivery = serializers.NullBooleanField() frequency_cap = serializers.IntegerField(required=False, allow_null=True) duration_in_days = ChoiceCaseInsensitiveField(choices=[1, 7, 30], required=False, allow_null=True) total_budget_amount_local_micro = serializers.CharField(required=False, allow_blank=True) daily_budget_amount_local_micro = serializers.CharField(required=True, allow_blank=True) status = ChoiceCaseInsensitiveField(choices=STATUS_CHOICES, required=False) created_at = DateTimeField(read_only=True) last_update = DateTimeField(read_only=True) advertiser_id = serializers.SerializerMethodField() cpi_target_goal = serializers.SerializerMethodField() line_item_count = serializers.SerializerMethodField() class Meta: model = TwitterCampaign fields = ('tw_campaign_id', 'tw_account_id', 'advertiser_id', 'campaign_id', 'name', 'funding_instrument_id', 'start_time', 'end_time', 'frequency_cap', 'standard_delivery', 'duration_in_days', 'total_budget_amount_local_micro', 'daily_budget_amount_local_micro', 'cpi_target_goal', 'line_item_count', 'status', 'created_at', 'last_update') validators = [ serializers.UniqueTogetherValidator( queryset=TwitterCampaign.objects_raw.all(), fields=('tw_campaign_id', 'name')) ] def get_advertiser_id(self, instance): return instance._advertiser_id def get_cpi_target_goal(self, instance): res = TwitterRevmap.objects.filter( tw_campaign_id=instance.tw_campaign_id).first() if res: return res.opt_value else: return '' def get_line_item_count(self, instance): return dict( all=TwitterLineItem.objects_raw.filter( tw_campaign_id=instance.pk).count(), active=TwitterLineItem.objects_raw.filter( tw_campaign_id=instance.pk).exclude(status='deleted').count())
class TwitterPromotedTweetSerializer(serializers.ModelSerializer): tw_line_item_id = PKRelatedField( queryset=TwitterLineItem.objects_raw.all()) tw_tweet_id = serializers.IntegerField() tw_app_card_id = serializers.IntegerField() status = ChoiceCaseInsensitiveField(choices=STATUS_CHOICES, required=False) created_at = DateTimeField(read_only=True) last_update = DateTimeField(read_only=True) preview = serializers.SerializerMethodField() app_card = serializers.SerializerMethodField() class Meta: model = TwitterPromotedTweet fields = ('tw_promoted_tweet_id', 'tw_line_item_id', 'tw_tweet_id', 'tw_app_card_id', 'preview', 'app_card', 'status', 'created_at', 'last_update') validators = [ serializers.UniqueTogetherValidator( queryset=TwitterPromotedTweet.objects_raw.all(), fields=('tw_promoted_tweet_id', )) ] def get_preview(self, instance): tweet = TwitterTweet.objects.filter( tw_tweet_id=instance.tw_tweet_id).first() if tweet: return tweet.text return '' def get_advertiser_id(self, instance): return instance._advertiser_id def get_app_card(self, instance): app_card = TwitterAppCard.objects.filter( tw_app_card_id=instance.tw_app_card_id).first() if app_card: video_player_url = '' if app_card.video_poster_url and app_card.video_url: video_player_url = "https://amp.twimg.com/amplify-web-player/prod/source.html?json_rpc=1&square_corners=1&image_src=%s&vmap_url=%s" % ( app_card.video_poster_url, app_card.video_url) return { 'type': app_card.card_type, 'image': app_card.wide_app_image, 'video_image': app_card.video_poster_url, 'video_map': app_card.video_url, 'video_player_url': video_player_url } return {}
class TwitterTweetSerializer(serializers.ModelSerializer): tw_twitter_user_id = serializers.IntegerField() text = serializers.CharField() created_at = DateTimeField(read_only=True) last_update = DateTimeField(read_only=True) class Meta: model = TwitterTweet fields = ('tw_tweet_id', 'tw_twitter_user_id', 'text', 'created_at', 'last_update') validators = [ serializers.UniqueTogetherValidator( queryset=TwitterTweet.objects_raw.all(), fields=('tw_tweet_id', )) ]
class TradingDeskSerializer(BaseModelSerializer): trading_desk_key = serializers.CharField(read_only=True) account_manager = serializers.IntegerField(required=True) status = ChoiceCaseInsensitiveField(choices=STATUS_CHOICES, required=True) email = EmailField(max_length=255, required=False, allow_blank=True) created_at = DateTimeField(read_only=True) last_update = DateTimeField(read_only=True) currency = ChoiceCaseInsensitiveField(choices=[ (x.currency_code, x.currency_code) for x in Currency.objects.all() ], required=True) # pylint: disable=old-style-class class Meta: model = TradingDesk
class EventSerializer(BaseModelSerializer): campaign = serializers.CharField(read_only=True) advertiser_id = serializers.IntegerField(read_only=True) advertiser = serializers.CharField(read_only=True) agency_id = serializers.IntegerField(read_only=True) agency = serializers.CharField(read_only=True) trading_desk_id = serializers.IntegerField(read_only=True) trading_desk = serializers.CharField(read_only=True) description = serializers.CharField(required=False, allow_blank=True) default_args = DefaultArgsTextField( required=False, validators=[BaseValidator.JSONValidator], allow_blank=True) max_frequency = serializers.IntegerField(required=False, allow_null=True) frequency_interval = serializers.IntegerField(required=False, default=None, allow_null=True) accept_unencrypted = serializers.BooleanField(required=False) encrypted_event_id = serializers.CharField(required=False, read_only=True) last_update = DateTimeField(read_only=True) # pylint: disable=old-style-class class Meta: model = Event validators = [ serializers.UniqueTogetherValidator( queryset=Event.objects_raw.all(), fields=('campaign_id', 'event')) ]
class BaseBidderListIdsSerializer(BaseModelSerializer): campaign_id = IntRelationField(required=False, related_model=Campaign, allow_zero=True) ad_group_id = IntRelationField(required=False, related_model=AdGroup, allow_zero=True) source_id = IntRelationField(required=True, related_model=Source, allow_zero=True) last_update = DateTimeField(read_only=True) placement_type = ChoiceCaseInsensitiveField(choices=PLACEMENT_TYPE_CHOICES, required=False) placement_id = serializers.CharField() size = serializers.CharField(required=False) tag = serializers.CharField(required=False, allow_blank=True) def is_valid(self, *args, **kwargs): errors = {} for field in ['ad_id', 'ad_group_id', 'campaign_id', 'source_id']: if field in self.fields and not BaseValidator.is_digit_or_none( self, field): errors[field] = 'Field should be a number' if errors: raise serializers.ValidationError(errors) valid = super(BaseBidderListIdsSerializer, self).is_valid(*args, **kwargs) campaign_id = self.validated_data.get('campaign_id', None) ad_group_id = self.validated_data.get('ad_group_id', None) source_id = self.validated_data.get('source_id', None) errors_key = {} if campaign_id and ad_group_id: true_campaign_id = AdGroup.objects_raw.get( pk=ad_group_id).campaign_id.pk if int(campaign_id) != int(true_campaign_id): message = 'Selected ad group(%s/%s) and campaign(%s) do not match' % ( ad_group_id, true_campaign_id, campaign_id) errors_key['ad_group_id, campaign_id'] = message if ad_group_id and not campaign_id: self.validated_data['campaign_id'] = Campaign.objects.get( adgroup=ad_group_id).campaign_id if not source_id or source_id == 0: errors_key['source_id'] = "You must select Source ID. Valid range from 1 to {0} inclusively" \ .format(Source.objects.count()) size = self.validated_data.get('size', None) if size and size == '375x50': errors_key['size'] = '375x50 size is currently unsupported' if errors_key: raise serializers.ValidationError(errors_key) return valid
class TwitterAppCardSerializer(serializers.ModelSerializer): tw_account_id = PKRelatedField(queryset=TwitterAccount.objects_raw.all()) name = serializers.CharField(required=True, allow_blank=False) card_type = MultipleChoiceField(choices=TW_CARD_TYPE, required=False) preview_url = serializers.CharField(required=True, allow_blank=False) app_country_code = serializers.CharField(required=False, allow_blank=False) iphone_app_id = serializers.CharField(required=False, allow_blank=False) ipad_app_id = serializers.CharField(required=False, allow_blank=False) googleplay_app_id = serializers.CharField(required=False, allow_blank=False) deep_link = serializers.CharField(required=False, allow_blank=False) custom_cta = MultipleChoiceField(choices=TW_CUSTOM_CTA, required=False) custom_icon_media_id = serializers.CharField(required=False, allow_blank=False) custom_app_description = serializers.CharField(required=False, allow_blank=False) image_media_id = serializers.CharField(required=False, allow_blank=False) wide_app_image_media_id = serializers.CharField(required=False, allow_blank=False) wide_app_image = serializers.CharField(required=False, allow_blank=False) video_id = serializers.CharField(required=False, allow_blank=False) video_url = serializers.CharField(required=False, allow_blank=False) video_poster_url = serializers.CharField(required=False, allow_blank=False) status = ChoiceCaseInsensitiveField(choices=STATUS_CHOICES, required=False) created_at = DateTimeField(read_only=True) last_update = DateTimeField(read_only=True) class Meta: model = TwitterAppCard fields = ('tw_app_card_id', 'tw_account_id', 'name', 'card_type', 'preview_url', 'app_country_code', 'iphone_app_id', 'ipad_app_id', 'googleplay_app_id', 'deep_link', 'custom_cta', 'custom_icon_media_id', 'custom_app_description', 'image_media_id', 'wide_app_image', 'wide_app_image_media_id', 'video_id', 'video_url', 'video_poster_url', 'status', 'created_at', 'last_update') validators = [ serializers.UniqueTogetherValidator( queryset=TwitterAppCard.objects_raw.all(), fields=('tw_tweet_id', )) ]
class AgencySerializer(BaseModelSerializer): trading_desk_id = PKRelatedField(queryset=TradingDesk.objects_raw.all()) trading_desk = serializers.CharField(read_only=True) agency_key = serializers.CharField(read_only=True) status = ChoiceCaseInsensitiveField(choices=STATUS_CHOICES, required=True) created_at = DateTimeField(read_only=True) email = EmailField(max_length=255, required=False, allow_blank=True) currency = ChoiceCaseInsensitiveField(choices=[ (x.currency_code, x.currency_code) for x in Currency.objects.all() ], required=True) account_manager = serializers.IntegerField(required=False) last_update = DateTimeField(read_only=True) # pylint: disable=old-style-class class Meta: model = Agency validators = [ serializers.UniqueTogetherValidator( queryset=Agency.objects_raw.all(), fields=('trading_desk_id', 'agency')) ] def is_valid(self, *args, **kwargs): valid = super(AgencySerializer, self).is_valid(*args, **kwargs) trading_desk = self.validated_data.get('trading_desk_id', False) account_manager = self.validated_data.get('account_manager', False) status = self.validated_data.get('status') if trading_desk and trading_desk.pk == MANAGE_TRADING_DESK_ID and not account_manager: raise serializers.ValidationError( 'account_manager field is required.') # Manage Agency if self.instance and self.instance.pk == 1: if self.instance.status != status: raise serializers.ValidationError( 'Forbidden to change status of the Manage agency.') return valid
class BaseBidderListSerializer(BaseReadonlySerializer): # pylint: disable=invalid-name id = serializers.IntegerField() advertiser_id = serializers.IntegerField() campaign_id = serializers.IntegerField() ad_group_id = serializers.IntegerField() source_id = serializers.IntegerField() campaign = serializers.CharField() ad_group = serializers.CharField() last_update = DateTimeField() placement_type = serializers.CharField() placement_id = serializers.CharField() size = serializers.CharField() tag = serializers.CharField()
class TwitterRevmapSerializer(BaseModelSerializer): campaign_id = PKRelatedField(queryset=Campaign.objects_raw.all()) tw_campaign_id = serializers.IntegerField() tw_line_item_id = serializers.IntegerField() opt_type = MultipleChoiceField(choices=OPT_TYPE_CHOICES, required=False) opt_value = serializers.DecimalField(max_digits=9, decimal_places=4, default=0, required=False) last_update = DateTimeField(read_only=True) # pylint: disable=old-style-class class Meta: model = TwitterRevmap
class RevmapSerializer(BaseModelSerializer): ad_group_id = PKRelatedField(queryset=AdGroup.objects_raw.all()) ad_group = serializers.CharField(required=True, allow_blank=False) campaign_id = serializers.IntegerField(required=True, allow_null=False) campaign = serializers.CharField(required=True, allow_blank=False) opt_type = MultipleChoiceField(choices=OPT_TYPE_CHOICES, required=False) opt_value = serializers.DecimalField(max_digits=9, decimal_places=4, default=0, required=False) rev_type = MultipleChoiceField(choices=OPT_TYPE_CHOICES, required=False) rev_value = serializers.DecimalField(max_digits=9, decimal_places=4, default=0, required=False) target_type = MultipleChoiceField(choices=OPT_TYPE_CHOICES, required=False) target_value = serializers.DecimalField(max_digits=9, decimal_places=4, default=0, required=False) last_update = DateTimeField(read_only=True) # pylint: disable=old-style-class class Meta: model = Revmap
class SearchSerializer(BaseReadonlySerializer): level = serializers.CharField(required=False) advertiser_id = serializers.IntegerField(required=False) advertiser = serializers.CharField(required=False) campaign_id = serializers.IntegerField(required=False) campaign = serializers.CharField(required=False) event_id = serializers.IntegerField(required=False) event = serializers.CharField(required=False) ad_group_id = serializers.IntegerField(required=False) ad_group = serializers.CharField(required=False) ad_id = serializers.IntegerField(required=False) agency = serializers.CharField(required=False) agency_id = serializers.IntegerField(required=False) trading_desk_id = serializers.IntegerField(required=False) # pylint: disable=invalid-name ad = serializers.CharField(required=False) last_update = DateTimeField(read_only=True)
class AuditLogSerializer(BaseModelSerializer): diff_field = models.TextField(blank=True) created = DateTimeField(read_only=True) # pylint: disable=unused-argument def create(self, *args, **kwargs): raise Exception('Can notcreate anything in', self.__class__.__name__) # pylint: disable=unused-argument def update(self, *args, **kwargs): raise Exception('Can not update anything in', self.__class__.__name__) # pylint: disable=old-style-class class Meta: model = AuditLog fields = ('audit_log_id', 'user', 'audit_type', 'audit_action', 'item_id', 'item_name', 'old_data', 'new_data', 'created', 'diff_field')
class CustomHintSerializer(BaseBidderListSerializer): ad_id = serializers.IntegerField() campaign_id = PKRelatedField(queryset=Campaign.objects_raw.all()) ad_group_id = PKRelatedField(queryset=AdGroup.objects_raw.all()) # pylint: disable=invalid-name ad = serializers.CharField() start_date = WideRangeDateTimeField() end_date = WideRangeDateTimeField() last_update = DateTimeField() tag = serializers.CharField() inflator_type = serializers.CharField() inflator = serializers.DecimalField(max_digits=9, decimal_places=6) priority = serializers.IntegerField() max_frequency = serializers.IntegerField() frequency_interval = serializers.IntegerField() # pylint: disable=old-style-class class Meta: model = CustomHint
class DiscretePricingIdsSerializer(BaseBidderListIdsSerializer): created_at = DateTimeField(read_only=True) campaign_id = IntRelationField(required=True, related_model=Campaign, allow_zero=True) ad_group_id = IntRelationField(required=True, related_model=AdGroup, allow_zero=True) last_update = DateTimeField(read_only=True) tag = serializers.CharField(required=False, allow_blank=True, allow_null=True) size = serializers.ChoiceField(choices=DISCRETE_PRICING_SIZE_CHOICES, required=False) rev_value = serializers.DecimalField(max_digits=9, decimal_places=2) # pylint: disable=old-style-class class Meta: model = DiscretePricingIds def is_valid(self, *args, **kwargs): errors_key = {} valid = super(DiscretePricingIdsSerializer, self).is_valid(*args, **kwargs) rev_value = self.validated_data.get('rev_value', None) size = self.validated_data.get('size', None) if size != 'all': errors_key['size'] = "Size: Not valid size. Size must be 'all'" if not rev_value or not float(rev_value): errors_key['rev_value'] = 'Field is required' elif float(rev_value) < 0: errors_key['rev_value'] = 'Rev value could not be negative' # CPI and CPC only [AMMYM-1713] campaign_id = self.validated_data.get('campaign_id', None) if not campaign_id: errors_key['campaign_id'] = 'Field is required' ad_group_id = self.validated_data.get('ad_group_id', None) if not ad_group_id: errors_key['ad_group_id'] = 'Field is required' source_id = self.validated_data.get('source_id', None) if not source_id: errors_key['source_id'] = 'Field is required' if ad_group_id: ad_group = AdGroup.objects.get(pk=ad_group_id) if not ad_group.revmap_rev_type in ['install', 'click']: errors_key[ 'ad_group_id'] = 'Discrete Pricing can be set ONLY for CPI and CPC Ad Groups' elif not errors_key: queryset = AdGroup.objects.all() if campaign_id: queryset = queryset.filter(campaign_id__id=campaign_id) error_ids = [] for ad_group in queryset: if not ad_group.revmap_rev_type in ['install', 'click']: error_ids.append(str(ad_group.pk)) if error_ids: errors_key[ 'campaign_id'] = 'Discrete Pricing can be set ONLY for CPI and CPC Ad Groups ({})'.format( ",".join(error_ids)) if errors_key: raise serializers.ValidationError(errors_key) return valid
class CampaignSerializer(BaseModelSerializer): # pylint: disable=old-style-class source_type = serializers.IntegerField(required=False) advertiser_id = PKRelatedField(queryset=Advertiser.objects_raw.all()) advertiser = serializers.CharField(read_only=True) agency_id = serializers.IntegerField(read_only=True) agency = serializers.CharField(read_only=True) trading_desk_id = serializers.IntegerField(read_only=True) trading_desk = serializers.CharField(read_only=True) viewthrough_url = serializers.CharField(required=False, allow_blank=True) iab_classification = MultipleChoiceField(choices=IAB_CATEGORIES, validators=[BaseValidator.required_validator], required=True) manage_classification = MultipleChoiceField(choices=MANAGE_CATEGORIES, required=False) other_iab_classification = MultipleChoiceField(choices=OTHER_IAB_CATEGORIES, required=False) frequency_map = JSONField(required=False, validators=[BaseValidator.JSONValidator], allow_blank=True) inflator_text = InflatorTextField(required=False, allow_blank=True) targeting = serializers.CharField(required=False, validators=[BaseValidator.JSONValidator], allow_blank=True) manage_budget_type = serializers.CharField(read_only=True) flight_start_date = ZeroDateTimeField(required=False, allow_null=True) flight_end_date = ZeroDateTimeField(required=False, allow_null=True) status = ChoiceCaseInsensitiveField(choices=STATUS_CHOICES, required=False) tracking_provider_id = PKRelatedField(required=False, queryset=TrackingProvider.objects_raw.all(), allow_null=True) daily_budget_type = ChoiceCaseInsensitiveField(choices=BUDGET_TYPES_CHOICES, required=False) flight_budget_type = ChoiceCaseInsensitiveField(choices=BUDGET_TYPES_CHOICES, required=False) last_update = DateTimeField(read_only=True) paused_at = DateTimeField(read_only=True) parent_name = serializers.CharField(read_only=True) agency_name = serializers.CharField(read_only=True) trading_desk_name = serializers.CharField(read_only=True) cpi_goal = serializers.SerializerMethodField() required_in_schema = ['status', 'app_install', 'priority'] permissions_extra_fields = ['targeting.min_slice', 'targeting.max_slice'] DOMAIN_MIN_LENGTH = 10 class Meta: model = Campaign fields = ('campaign_id', 'campaign', 'source_type', 'advertiser_id', 'advertiser', 'agency_id', 'agency', 'trading_desk_id', 'trading_desk', 'notes', 'sampling_rate', 'throttling_rate', 'domain', 'redirect_url', 'destination_url', 'viewthrough_url', 'tracking_provider_id', 'inflator_text', 'frequency_map', 'priority', 'daily_budget_type', 'daily_budget_value', 'daily_spend', 'status', 'parent_name', 'agency_name', 'trading_desk_name', 'agency_id', 'trading_desk_id', 'distribution_app_sha1_mac', 'distribution_app_sha1_udid', 'distribution_app_sha1_android_id', 'distribution_app_ifa', 'distribution_app_md5_ifa', 'distribution_app_xid', 'distribution_web', 'flight_start_date', 'flight_end_date', 'flight_budget_type', 'flight_budget_value', 'attribution_window', 'genre', 'last_update', 'paused_at', 'external_id', 'categories', 'targeting', 'capped', 'iab_classification', 'manage_classification', 'other_iab_classification', 'manage_budget_type', 'total_cost_cap', 'daily_cost_cap', 'total_loss_cap', 'daily_loss_cap', 'app_install', 'overridden', 'ignore_fatigue_segment', 'ignore_suppression_segment', 'ad_groups_total', 'ad_groups_enabled', 'ads_total', 'ads_enabled', 'ads_disapproved', 'black_lists_total', 'black_lists_campaign', 'black_lists_ad_group', 'white_lists_total', 'white_lists_campaign', 'white_lists_ad_group', 'custom_hints_total', 'custom_hints_campaign', 'custom_hints_ad_group', 'discrete_pricing_total', 'discrete_pricing_campaign', 'discrete_pricing_ad_group', 'cpi_goal', 'ads_to_pause', 'ads_to_delete', 'a9_pending', 'a9_failed') validators = [ serializers.UniqueTogetherValidator( queryset=Campaign.objects_raw.all(), fields=('advertiser_id', 'campaign') ) ] def _is_valid_frequency_map(self, errors): frequency_map = self.validated_data.get('frequency_map', None) if frequency_map: try: frequency_map_json = json.loads(frequency_map) if '375x50' in frequency_map_json: errors['frequency_map'] = '375x50 size is currently unsupported' except Exception: pass def _is_valid_no_distributions(self, errors): # at least one Distribution must be selected distribution_fields = ('distribution_app_sha1_android_id', 'distribution_app_ifa', 'distribution_web') has_distributions = False for field in distribution_fields: has_distributions |= self.validated_data.get(field, False) if not has_distributions: errors['distributions'] = 'No distributions' def _is_valid_domain(self, errors): domain = self.validated_data.get('domain', None) if domain and not re.match("^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|" + "([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|" + "([a-zA-Z0-9][a-zA-Z0-9-_]{1,61}[a-zA-Z0-9]))\." + "([a-zA-Z]{2,6}|[a-zA-Z0-9-]{2,30}\.[a-zA-Z]{2,3})$", domain): errors['domain'] = 'should not contain http:// or https:// and must be valid url' def _is_valid_flight_budget_data(self, errors): if self.validated_data.get('flight_start_date') and self.validated_data.get('flight_end_date'): if self.validated_data.get('flight_start_date') > self.validated_data.get('flight_end_date'): errors['flight start/end dates'] = 'flight start date need to be before flight end date' if self.validated_data.get('daily_budget_value') or self.validated_data.get('flight_budget_value'): if not self.validated_data.get('flight_budget_type'): errors['flight budget type'] = 'You must set Flight Budget Type' if self.validated_data.get('flight_budget_value') or self.validated_data.get('total_cost_cap') \ or self.validated_data.get('total_loss_cap'): if not self.validated_data.get('flight_start_date'): errors['flight start date'] = 'You must set Flight Start Date' flighting_budget_fields = ('daily_budget_value', 'flight_budget_value', 'total_cost_cap', 'total_loss_cap', 'daily_cost_cap', 'daily_loss_cap') flighting_budget_values = {field: self.validated_data.get(field, None) >= 0 for field in flighting_budget_fields} for k, v in flighting_budget_values.items(): if not v: errors[k] = 'Must be positive number' def _is_valid_attribution_window(self, errors): attribution_window = self.validated_data.get('attribution_window', None) if attribution_window and not attribution_window >= 0: errors['attribution_window'] = 'Must be positive number' def _is_valid_app_install(self, errors): targeting = self.validated_data.get('targeting') targeting = json.loads(targeting) if targeting else {} destination_url = self.validated_data.get('destination_url', None) # AMMYM-2708 if self.validated_data.get('app_install', None) and "os" in targeting: os = targeting["os"] if os == "iOS" and not destination_url.startswith('https://itunes.apple.com'): errors['destination_url'] = 'Destination URL must start w/: https://itunes.apple.com' if os == "Android" and not destination_url.startswith('https://play.google.com'): errors['destination_url'] = 'Destination URL must start w/: https://play.google.com' def _is_valid_urls(self, errors): ignore_fatigue_segment = self.validated_data.get('ignore_fatigue_segment', None) ignore_suppression_segment = self.validated_data.get('ignore_suppression_segment', None) destination_url = self.validated_data.get('destination_url', None) redirect_url = self.validated_data.get('redirect_url', None) viewthrough_url = self.validated_data.get('viewthrough_url', None) if viewthrough_url and viewthrough_url.find('https://') == -1: errors['impression url'] = 'insecure URL, use https://' if destination_url and len(destination_url) < self.DOMAIN_MIN_LENGTH: errors['destination_url'] = 'Must be at least {0} characters long'.format(self.DOMAIN_MIN_LENGTH) if not ignore_fatigue_segment and not ignore_suppression_segment: if redirect_url and redirect_url.find('https://') == -1: errors['click url'] = 'insecure URL, use https://' def _is_valid_priority(self, errors): if self.validated_data.get('priority'): if self.validated_data.get('priority') > 100 or self.validated_data.get('priority') < 0: errors['priority'] = 'Priority can not be less than 0 and more than 100' def _is_valid_genre(self, errors): if not self.validated_data.get('genre', None): errors['genre'] = 'Field is required' def _is_valid_app_classifications(self, errors): app_install = self.validated_data.get('app_install', None) manage_classification = self.validated_data.get('manage_classification', None) if app_install is True and not manage_classification: errors['app classifications'] = 'Field is required if app install yes' def _get_status(self): return self.validated_data.get('status', None) def _is_valid_inflator(self, errors): inflator_valid, error_message = InflatorTextField.is_valid(self.validated_data.get('inflator_text')) if not inflator_valid: errors['inflator_text'] = error_message def _is_valid_base(self, *args, **kwargs): return super(CampaignSerializer, self).is_valid(*args, **kwargs) def is_valid(self, *args, **kwargs): if not self.initial_data.get('advertiser_id', None): raise serializers.ValidationError({'advertiser_id': 'Field is required'}) errors = {} valid = self._is_valid_base(*args, **kwargs) status = self._get_status() if status is None and self.instance: status = self.instance.status validate_only = False request = self.context.get('request', None) if request and 'validate_only' in request.query_params: validate_only = True self._is_valid_frequency_map(errors) self._is_valid_no_distributions(errors) self._is_valid_flight_budget_data(errors) self._is_valid_domain(errors) self._is_valid_app_install(errors) self._is_valid_urls(errors) self._is_valid_priority(errors) self._is_valid_genre(errors) self._is_valid_app_classifications(errors) self._is_valid_inflator(errors) self._is_valid_attribution_window(errors) # tracking_provider_id = self.validated_data.get('tracking_provider_id', None) # if app_install is True and not tracking_provider_id: # errors['tracking_provider_id'] = 'Field is required if app install yes' if validate_only and self.instance and status in [STATUS_ENABLED, STATUS_PAUSED]: # check adgroups err = {} queryset = AdGroup.objects.filter(campaign_id=self.instance) if validate_only: queryset = queryset.filter(status__in=[STATUS_ENABLED, STATUS_PAUSED]) else: queryset = queryset.filter(status=STATUS_ENABLED) for instance in queryset: serializer = AdGroupSerializer(data=instance.as_dict(), context=self.context, instance=instance) valid = serializer.is_valid(raise_exception=False, skip_ads_validation=True) if not valid: err[instance.pk] = serializer._errors if err: errors['bad_adgroups'] = err # check ads err = {} queryset = Ad.objects.filter(ad_group_id__campaign_id=self.instance) if validate_only: queryset = queryset.filter(status__in=[STATUS_ENABLED, STATUS_PAUSED]) else: queryset = queryset.filter(status=STATUS_ENABLED) for instance in queryset: serializer = AdSerializer(data=instance.as_dict(), context=self.context, instance=instance) valid = serializer.is_valid(raise_exception=False) if not valid: err[instance.pk] = serializer._errors if err: errors['bad_ads'] = err if errors: raise serializers.ValidationError(errors) if valid: self._prepare_categories_data() return valid def _prepare_categories_data(self): categories = self.validated_data.pop('iab_classification', []) categories += self.validated_data.pop('manage_classification', []) categories += self.validated_data.pop('other_iab_classification', []) value = [] for element in categories: if element not in value: value.append(element) self.validated_data['categories'] = ' '.join(value) def update(self, instance, *args, **kwargs): request = self.context.get('request', None) if request and 'validate_only' in request.query_params: return instance return super(CampaignSerializer, self).update(instance, *args, **kwargs) def create(self, *args, **kwargs): campaign = super(CampaignSerializer, self).create(*args, **kwargs) event_ibid = Event(campaign_id=campaign, event='i-bid', description='Impression bid.', default_args='', base_event_id=0, deleted=False, max_frequency=None, frequency_interval=None, show_in_stats=True, accept_unencrypted=False) event_ibid.save() event_impression = Event(campaign_id=campaign, event='impression', description='Ad impression.', default_args='', base_event_id=event_ibid.event_id, deleted=False, max_frequency=1, frequency_interval=86400, show_in_stats=True, accept_unencrypted=False) event_impression.save() event_click = Event(campaign_id=campaign, event='click', description='Ad click.', default_args='', base_event_id=event_impression.event_id, deleted=False, max_frequency=None, frequency_interval=None, show_in_stats=True, accept_unencrypted=False) event_click.save() event_install = Event(campaign_id=campaign, event='install', description='App install.', default_args='', base_event_id=event_click.event_id, deleted=False, max_frequency=1, frequency_interval=None, show_in_stats=True, accept_unencrypted=False) event_install.save() return campaign def to_representation(self, instance, **kwargs): to_representation_dict = super(CampaignSerializer, self).to_representation(instance, **kwargs) user = REGISTRY.get('user', None) try: data = json.loads(to_representation_dict.get('targeting'), object_pairs_hook=OrderedDict) fields = ["targeting.%s" % f for f in data.keys()] permitted_fields = user.get_permitted_instance_fields(instance=instance, action='read', fields=fields) for k in data.keys(): if "targeting.%s" % k not in permitted_fields: del data[k] except: data = None to_representation_dict['targeting'] = '' if data is None else json.dumps(data) return to_representation_dict def get_cpi_goal(self, instance, **kwargs): if instance.source_type == 2: revmap = TwitterRevmap.objects.filter(campaign_id=instance.campaign_id).first() if revmap: return revmap.opt_value else: return '' else: return ''
class UserSerializer(BaseModelSerializer): username = EmailField(max_length=75, allow_blank=False) first_name = serializers.CharField(max_length=30, allow_blank=True) last_name = serializers.CharField(max_length=30, allow_blank=True) email = EmailField(max_length=75, allow_blank=False) is_active = serializers.BooleanField(required=True) trading_desk = serializers.CharField(required=True, allow_blank=False) is_manage_user = serializers.BooleanField(read_only=True) date_joined = DateTimeField(read_only=True) user_in_groups = ListField(required=True) groups = serializers.ManyRelatedField( required=False, child_relation=serializers.PrimaryKeyRelatedField( queryset=Group.objects.all())) reset_password_url = serializers.CharField(required=True) required_in_schema = ['first_name', 'last_name', 'groups'] # pylint: disable=old-style-class class Meta: model = User fields = ('user_id', 'username', 'first_name', 'last_name', 'full_name', 'email', 'is_active', 'user_in_groups', 'groups', 'trading_desk', 'trading_desk_id', 'is_manage_user', 'date_joined', 'reset_password_url') def to_representation(self, instance, **kwargs): to_representation_dict = super(UserSerializer, self).to_representation( instance, **kwargs) user_in_groups = [] if 'user_in_groups' in to_representation_dict: for group in to_representation_dict['user_in_groups']: if group in TD_GROUPS or group in MANAGE_GROUPS: user_in_groups.append(group) to_representation_dict['user_in_groups'] = user_in_groups if 'groups' in to_representation_dict: del to_representation_dict['groups'] if 'reset_password_url' in to_representation_dict: del to_representation_dict['reset_password_url'] return to_representation_dict @transaction.atomic() def create(self, request): reset_password_url = request.pop('reset_password_url', None) if reset_password_url is None: raise serializers.ValidationError( {'reset_password_url': 'This field is required'}) if reset_password_url.find('{token}') == -1 or reset_password_url.find( '{username}') == -1: raise serializers.ValidationError({ 'reset_password_url': 'This field must contain {token} and {username} placeholders' }) groups = list( Group.objects.filter( pk__in=[int(x.pk) for x in request.pop('groups', None)])) if groups: AuditLogger.skip_next_write = True user = super(UserSerializer, self).create(request) user.date_joined = datetime.now(tzutc()) user.groups.add(*groups) else: raise serializers.ValidationError( {'groups': 'User should belong to group'}) UserProfile.objects.create(user=user) obj = TradingDesk.objects.filter( trading_desk=self.initial_data.get('trading_desk', None)) if obj.exists(): user.profile.trading_desk.add(obj.first()) else: user.save() # save auditlog raise serializers.ValidationError( {'trading_desk': 'User should belong to trading desk'}) user.save() # save auditlog reset_password_url = re.sub(r'\{token\}', user.get_reset_password_hash(), reset_password_url) reset_password_url = re.sub(r'\{username}', user.username, reset_password_url) attrs = {'user': user, 'reset_password_url': reset_password_url} send_mail(mail_to=user.username, template="new_user", attrs=attrs, mail_from="%s <*****@*****.**>" % user.trading_desk) return user @transaction.atomic() def update(self, instance, validated_data): data = deepcopy(validated_data) groups = data.pop('groups', []) AuditLogger.skip_next_write = True super(UserSerializer, self).update(instance, data) new_user_in_groups = [x.name for x in groups] old_user_in_groups = [x.name for x in instance.groups.all()] new_groups = list( Group.objects.filter(name__in=[ x for x in [x for x in new_user_in_groups if x not in old_user_in_groups] ])) old_groups = list( Group.objects.filter(name__in=[ x for x in [x for x in old_user_in_groups if x not in new_user_in_groups] ])) if old_groups: [ x.user_set.remove(instance) for x in old_groups if x.name in MANAGE_GROUPS or x.name in TD_GROUPS ] if new_groups: instance.groups.add(*new_groups) instance.save() # save auditlog return instance def is_valid(self, *args, **kwargs): valid = super(UserSerializer, self).is_valid(*args, **kwargs) instance = self.instance # username username = self.validated_data.get('username') if re.match(r'[^a-zA-Z0-9_\@\+\.\-]', username): raise serializers.ValidationError({ 'username': '******' }) user = REGISTRY['user'] user_groups = [g.name for g in user.groups.all()] user_in_groups = [] groups = self.validated_data.get('groups', None) if groups: user_in_groups = list([ x.name for x in Group.objects.filter( pk__in=[int(x.pk) for x in groups]) ]) if instance: instance_groups = [g.name for g in instance.groups.all()] instance_monarch_groups = [ g for g in instance_groups if str(g) in MANAGE_GROUPS + TD_GROUPS ] errors = {} for field in ['first_name', 'last_name']: if not self.validated_data.get(field, None) and getattr( self.instance, field): errors[field] = 'This field is required' if instance_monarch_groups and not self.validated_data.get( 'groups', None): errors['user_in_groups'] = 'This field is required' if errors: raise serializers.ValidationError(errors) if TD_ACCOUNT_MANAGER_GROUP in user_groups: for group in user_in_groups: if group not in TD_GROUPS: raise serializers.ValidationError('Available groups:%s' % TD_GROUPS.join(',')) obj = TradingDesk.objects.filter( trading_desk=self.initial_data.get('trading_desk', None)) if obj.exists(): obj = obj.first() else: raise serializers.ValidationError( {'trading_desk': 'Invalid value'}) group_list = TD_GROUPS[:] if obj.pk == MANAGE_TRADING_DESK_ID: group_list += MANAGE_GROUPS for group in user_in_groups: if group not in group_list: raise serializers.ValidationError('Available groups:%s' % ','.join(group_list)) return valid
class AdGroupSerializer(BaseModelSerializer): # pylint: disable=old-style-class campaign_id = PKRelatedField(queryset=Campaign.objects_raw.all()) campaign = serializers.CharField(read_only=True) advertiser_id = serializers.IntegerField(read_only=True) advertiser = serializers.CharField(read_only=True) agency_id = serializers.IntegerField(read_only=True) agency = serializers.CharField(read_only=True) trading_desk_id = serializers.IntegerField(read_only=True) trading_desk = serializers.CharField(read_only=True) destination_url = serializers.CharField(required=False, allow_blank=True) viewthrough_url = serializers.CharField(required=False, allow_blank=True) app_install = serializers.BooleanField(read_only=True) frequency_map = JSONField(required=False, validators=[BaseValidator.JSONValidator], allow_blank=True) inflator_text = InflatorTextField(required=False, allow_blank=False) targeting = serializers.CharField(required=False, validators=[BaseValidator.JSONValidator], allow_blank=True) event_args = serializers.CharField( required=False, validators=[BaseValidator.JSONValidator], allow_blank=True) flight_start_date = ZeroDateTimeField(required=False, allow_null=True) flight_end_date = ZeroDateTimeField(required=False, allow_null=True) status = ChoiceCaseInsensitiveField(choices=STATUS_CHOICES, required=False) daily_budget_type = ChoiceCaseInsensitiveField( choices=BUDGET_TYPES_CHOICES, required=False) flight_budget_type = ChoiceCaseInsensitiveField( choices=BUDGET_TYPES_CHOICES, required=False) last_update = DateTimeField(read_only=True) paused_at = DateTimeField(read_only=True) parent_name = serializers.CharField(read_only=True) revmap_rev_type = ChoiceCaseInsensitiveField(choices=OPT_TYPE_CHOICES, required=False) revmap_rev_value = serializers.DecimalField(max_digits=9, decimal_places=4, default=0, required=False) revmap_opt_type = ChoiceCaseInsensitiveField(choices=OPT_TYPE_CHOICES, required=False) revmap_opt_value = serializers.DecimalField(max_digits=9, decimal_places=4, default=0, required=False) required_in_schema = ['priority'] permissions_extra_fields = [ 'targeting.state', 'targeting.city', 'targeting.zip_code', 'targeting.min_slice', 'targeting.max_slice' ] DOMAIN_MIN_LENGTH = 10 class Meta: model = AdGroup fields = ('ad_group_id', 'campaign_id', 'campaign', 'advertiser_id', 'advertiser', 'agency_id', 'agency', 'trading_desk_id', 'trading_desk', 'app_install', 'frequency_map', 'inflator_text', 'targeting', 'event_args', 'flight_start_date', 'flight_end_date', 'parent_name', 'ad_group', 'ad_group_type', 'categories', 'domain', 'notes', 'redirect_url', 'destination_url', 'viewthrough_url', 'revmap_rev_type', 'revmap_rev_value', 'revmap_opt_type', 'revmap_opt_value', 'priority', 'daily_budget_type', 'daily_budget_value', 'daily_spend', 'capped', 'hourly_capped', 'status', 'tag', 'flight_budget_type', 'flight_budget_value', 'sampling_rate', 'throttling_rate', 'max_frequency', 'frequency_interval', 'bidder_args', 'last_update', 'paused_at', 'distribution_app_sha1_android_id', 'distribution_app_ifa', 'distribution_web', 'total_cost_cap', 'daily_cost_cap', 'total_loss_cap', 'daily_loss_cap', 'overridden', 'ignore_fatigue_segment', 'ignore_suppression_segment', 'ads_enabled', 'ads_disapproved', 'ads_total', 'black_lists_total', 'white_lists_total', 'custom_hints_total', 'discrete_pricing_total', 'ads_to_delete', 'ads_to_pause', 'a9_pending', 'a9_failed') validators = [ serializers.UniqueTogetherValidator( queryset=AdGroup.objects_raw.all(), fields=('campaign_id', 'ad_group')) ] def _is_valid_frequency_map_375x50_is_not_supported(self, errors): frequency_map = self.validated_data.get('frequency_map', None) if frequency_map: try: frequency_map_json = json.loads(frequency_map) if '375x50' in frequency_map_json: errors[ 'frequency_map'] = '375x50 size is currently unsupported' except Exception: pass def _is_valid_no_distributions(self, errors): distribution_fields = ('distribution_app_sha1_android_id', 'distribution_app_ifa', 'distribution_web') distribution_values = [ self.validated_data.get(field, None) for field in distribution_fields if self.validated_data.get(field, None) != None ] if distribution_values and (True not in distribution_values): errors['distributions'] = 'No distributions' def _is_valid_rev_value_negative(self, errors): revmap_rev_value = self.validated_data.get('revmap_rev_value', None) flighting_budget_fields = ('daily_budget_value', 'flight_budget_value', 'total_cost_cap', 'total_loss_cap', 'daily_cost_cap', 'daily_loss_cap') flighting_budget_values = { field: self.validated_data.get(field, None) >= 0 for field in flighting_budget_fields } for k, v in flighting_budget_values.items(): if not v: errors[k] = 'Must be positive number' if revmap_rev_value and float(revmap_rev_value) < 0: errors['revmap_rev_value'] = 'Rev value could not be negative' def _is_valid_insecure_url(self, errors): redirect_url = self.validated_data.get('redirect_url', None) viewthrough_url = self.validated_data.get('viewthrough_url', None) ignore_fatigue_segment = self.validated_data.get( 'ignore_fatigue_segment', None) ignore_suppression_segment = self.validated_data.get( 'ignore_suppression_segment', None) if not ignore_fatigue_segment and not ignore_suppression_segment: if redirect_url and redirect_url.find('https://') == -1: errors['redirect_url'] = 'insecure URL, use https://' if viewthrough_url and viewthrough_url.find('https://') == -1: errors['impression url'] = 'insecure URL, use https://' def _is_valid_destination_url(self, errors): destination_url = self.validated_data.get('destination_url', None) if destination_url and len(destination_url) < self.DOMAIN_MIN_LENGTH: errors[ 'destination_url'] = 'Must be at least {0} characters long'.format( self.DOMAIN_MIN_LENGTH) def _is_valid_different_segments(self, errors): ignore_fatigue_segment = self.validated_data.get( 'ignore_fatigue_segment', None) ignore_suppression_segment = self.validated_data.get( 'ignore_suppression_segment', None) if ignore_fatigue_segment != ignore_suppression_segment: errors[ 'ignore_fatigue_segment'] = 'ignore_fatigue_segment should be the same as ignore_suppression_segment' errors[ 'ignore_suppression_segment'] = 'ignore_fatigue_segment should be the same as ignore_suppression_segment' def _is_valid_targeting(self, errors): destination_url = self.validated_data.get('destination_url', None) targeting = self.validated_data.get('targeting') targeting = json.loads(targeting) if targeting else {} ignore_fatigue_segment = self.validated_data.get( 'ignore_fatigue_segment', None) ignore_suppression_segment = self.validated_data.get( 'ignore_suppression_segment', None) campaign = self.validated_data.get('campaign_id') app_install = campaign.app_install parent_targeting = json.loads( campaign.targeting) if campaign and campaign.targeting else {} if not parent_targeting.get('country', False): if not targeting.get('country', False): errors['country'] = 'You must set County' if not parent_targeting.get('os', False) and not targeting.get( 'os', False): errors[ 'os'] = "OS must be defined in its or Parent's entity targeting" if parent_targeting.get('os', False) == OS_CHOICES[0][0] or targeting.get( 'os', False) == OS_CHOICES[0][0]: if not parent_targeting.get('model', False) and not targeting.get( 'model', False): errors['model'] = 'You must set Model of your device' if parent_targeting.get('os', False) == OS_CHOICES[0][0] or targeting.get( 'os', False) == OS_CHOICES[0][0]: if self.validated_data.get('distribution_app_sha1_android_id', False) == True: errors[ 'distribution_app_sha1_android_id'] = 'Cant be selected if os = iOS' if parent_targeting.get('os', False) == OS_CHOICES[1][0] or targeting.get( 'os', False) == OS_CHOICES[1][0]: if self.validated_data.get('distribution_app_ifa', False) == True: errors[ 'distribution_app_ifa'] = 'Cant be selected if os = Android' if parent_targeting.get('os', False) == OS_CHOICES[2][0] or targeting.get( 'os', False) == OS_CHOICES[2][0]: if self.validated_data.get('distribution_app_ifa', False) == True: errors[ 'distribution_app_ifa'] = 'Cant be selected if os = Kindle' if app_install and not ignore_fatigue_segment and not ignore_suppression_segment: if destination_url and "os" in parent_targeting: os = parent_targeting["os"] if os == "iOS" and not destination_url.startswith( 'https://itunes.apple.com'): errors[ 'destination_url'] = 'Destination URL must start w/: https://itunes.apple.com' if os == "Android" and not destination_url.startswith( 'https://play.google.com'): errors[ 'destination_url'] = 'Destination URL must start w/: https://play.google.com' def _is_valid_flightings(self, errors): if self.validated_data.get( 'flight_start_date') and self.validated_data.get( 'flight_end_date'): if self.validated_data.get( 'flight_start_date') > self.validated_data.get( 'flight_end_date'): errors[ 'flight_start_date'] = 'flight start date need to be before flight end date' if self.validated_data.get('priority'): if self.validated_data.get( 'priority') > 100 or self.validated_data.get( 'priority') < 0: errors[ 'priority'] = 'Priority can not be less than 0 and more than 100' if self.validated_data.get( 'daily_budget_value') or self.validated_data.get( 'flight_budget_value'): if not self.validated_data.get('flight_budget_type'): errors[ 'flight_budget_type'] = 'You must set Flight Budget Type' if self.validated_data.get( 'flight_budget_value') or self.validated_data.get( 'total_cost_cap') or self.validated_data.get( 'total_loss_cap'): if not self.validated_data.get('flight_start_date'): errors['flight_start_date'] = 'You must set Flight Start Date' def _is_valid_inflator(self, errors): (inflator_valid, error_message) = InflatorTextField.is_valid( self.validated_data.get('inflator_text')) if not inflator_valid: errors['inflator_text'] = error_message def _get_status(self): return self.validated_data.get('status', None) def _get_campaign_id(self): return self.validated_data.get('campaign_id') def _is_valid_base(self, *args, **kwargs): return super(AdGroupSerializer, self).is_valid(*args, **kwargs) def is_valid(self, *args, **kwargs): skip_ads_validation = kwargs.pop('skip_ads_validation', False) raise_exception = kwargs.pop('raise_exception', True) valid = self._is_valid_base(*args, **kwargs) if not valid: if raise_exception: raise serializers.ValidationError(self.errors) else: self._errors = self.errors return False errors = {} campaign = self._get_campaign_id() status = self._get_status() if status is None and self.instance: status = self.instance.status validate_only = False request = self.context.get('request', None) if request and 'validate_only' in request.query_params: validate_only = True self._is_valid_frequency_map_375x50_is_not_supported(errors) self._is_valid_no_distributions(errors) self._is_valid_rev_value_negative(errors) self._is_valid_insecure_url(errors) self._is_valid_destination_url(errors) self._is_valid_different_segments(errors) self._is_valid_targeting(errors) self._is_valid_flightings(errors) self._is_valid_inflator(errors) if valid \ and not skip_ads_validation \ and validate_only \ and self.instance \ and status in [STATUS_ENABLED, STATUS_PAUSED] \ and campaign.status in [STATUS_ENABLED, STATUS_PAUSED]: err = {} queryset = Ad.objects.filter(ad_group_id=self.instance) if validate_only: queryset = queryset.filter( status__in=[STATUS_ENABLED, STATUS_PAUSED]) else: queryset = queryset.filter(status=STATUS_ENABLED) for instance in queryset: serializer = AdSerializer(data=instance.as_dict(), context=self.context, instance=instance) valid = serializer.is_valid(raise_exception=False) if not valid: err[instance.pk] = serializer._errors if err: errors['bad_ads'] = err if errors: if raise_exception: raise serializers.ValidationError(errors) else: self._errors = errors valid = False return valid def to_representation(self, instance, **kwargs): to_representation_dict = super(AdGroupSerializer, self).to_representation( instance, **kwargs) user = REGISTRY.get('user', None) try: data = json.loads(to_representation_dict.get('targeting'), object_pairs_hook=OrderedDict) except: data = None else: fields = ["targeting.%s" % f for f in data.keys()] permitted_fields = user.get_permitted_instance_fields( instance=instance, action='read', fields=fields) for k in data.keys(): if "targeting.%s" % k not in permitted_fields: del data[k] to_representation_dict[ 'targeting'] = '' if data is None else json.dumps(data) return to_representation_dict def update(self, instance, *args, **kwargs): request = self.context.get('request', None) if request and 'validate_only' in request.query_params: return instance if self.validated_data.get('revmap_rev_type', None) not in ['install', 'click']: objs = DiscretePricingIds.objects.filter(ad_group_id=instance.pk) for obj in objs: obj.delete() return super(AdGroupSerializer, self).update(instance, *args, **kwargs)
class TwitterLineItemSerializer(BaseModelSerializer): tw_campaign_id = PKRelatedField(queryset=TwitterCampaign.objects_raw.all()) name = serializers.CharField(required=True, allow_blank=False) currency = serializers.CharField(required=True, allow_blank=False) product_type = ChoiceCaseInsensitiveField(choices=TW_PRODUCT_TYPES, required=False) placements = serializers.CharField( required=False, validators=[BaseValidator.JSONValidator], allow_blank=True) primary_web_event_tag = serializers.CharField(required=True, allow_blank=False) objective = ChoiceCaseInsensitiveField(choices=TW_OBJECTIVES, required=False) bid_amount_local_micro = serializers.CharField(required=True, allow_blank=False) bid_amount_computed = serializers.CharField(required=True, allow_blank=False) bid_override = serializers.NullBooleanField() bid_type = ChoiceCaseInsensitiveField(choices=TW_BID_TYPES, required=False) bid_unit = ChoiceCaseInsensitiveField(choices=TW_BID_UNITS, required=False) optimization = ChoiceCaseInsensitiveField(choices=TW_OPTIMIZATIONS, required=False) charge_by = ChoiceCaseInsensitiveField(choices=TW_CHARGE_BYS, required=False) categories = serializers.CharField( required=False, validators=[BaseValidator.JSONValidator], allow_blank=True) tracking_tags = serializers.CharField( required=False, validators=[BaseValidator.JSONValidator], allow_blank=True) automatically_select_bid = serializers.NullBooleanField() total_budget_amount_local_micro = serializers.CharField(required=False, allow_blank=True) daily_budget_amount_local_micro = serializers.CharField(required=False, allow_blank=True) promoted_tweets = serializers.SerializerMethodField() targeting = serializers.SerializerMethodField() tw_campaign_name = serializers.SerializerMethodField() cpi_target_goal = serializers.SerializerMethodField() status = ChoiceCaseInsensitiveField(choices=STATUS_CHOICES, required=False) created_at = DateTimeField(read_only=True) last_update = DateTimeField(read_only=True) advertiser_id = serializers.SerializerMethodField() campaign_id = serializers.SerializerMethodField() tailored_audience = serializers.SerializerMethodField() class Meta: model = TwitterLineItem fields = ('tw_line_item_id', 'tw_campaign_id', 'tw_campaign_name', 'advertiser_id', 'campaign_id', 'name', 'currency', 'start_time', 'end_time', 'product_type', 'placements', 'primary_web_event_tag', 'objective', 'bid_amount_local_micro', 'bid_amount_computed_reason', 'bid_amount_computed', 'bid_override', 'bid_type', 'bid_unit', 'optimization', 'charge_by', 'categories', 'tracking_tags', 'automatically_select_bid', 'total_budget_amount_local_micro', 'daily_budget_amount_local_micro', 'status', 'created_at', 'last_update', 'promoted_tweets', 'targeting', 'cpi_target_goal', 'tailored_audience') validators = [ serializers.UniqueTogetherValidator( queryset=TwitterLineItem.objects_raw.all(), fields=('tw_capaign_id', 'name')) ] def get_advertiser_id(self, instance): return instance._advertiser_id def get_campaign_id(self, instance): return instance._campaign_id def get_tw_campaign_name(self, instance): return instance.tw_campaign_id.name def get_promoted_tweets(self, instance): promoted_tweets = TwitterPromotedTweet.objects.filter( tw_line_item_id=instance.tw_line_item_id).all() return TwitterPromotedTweetSerializer(promoted_tweets, many=True).data def get_cpi_target_goal(self, instance): res = TwitterRevmap.objects.filter( tw_line_item_id=instance.tw_line_item_id).first() if res: return res.opt_value else: return '' def get_targeting(self, instance): targetings = TwitterTargeting.objects.filter( tw_line_item_id=instance.tw_line_item_id).exclude( tw_targeting_type=18).all() ret = {} for t in targetings: type_name = type_names.get(t.tw_targeting_type) # if targeting_type is not supported one, continue if not type_name: continue if not ret.get('platform') and platform_dep_cls_names.get( type_name): _cls = platform_dep_cls_names[type_name] item = _cls.objects_raw.filter( tw_targeting_id=t.tw_targeting_id).first() if item: ret['platform'] = [item.platform] if not ret.get(type_name): ret[type_name] = [] if t.tw_targeting_type == 22: behavior = TwitterBehavior.objects_raw.filter( tw_targeting_id=t.tw_targeting_id).first() """ if behavior: temp = [] taxonomy = behavior.tw_behavior_taxonomy if taxonomy.parent: temp.append(taxonomy.parent.name) temp.append(taxonomy.name) temp.append(behavior.name) ret[type_name].append(' > '.join(temp)) """ ret[type_name].append(behavior.name) elif t.tw_targeting_type == 23: event = TwitterEvent.objects_raw.filter( tw_targeting_id=t.tw_targeting_id).first() if event: ret[type_name].append(event.name) else: ret[type_name].append(t.name) return ret def get_tailored_audience(self, instance): res = {} targetings = TwitterTargeting.objects.filter( tw_line_item_id=instance.tw_line_item_id, tw_targeting_type=18).all() for targeting in targetings: if not res.get(targeting.name): res[targeting.name] = [] audience = TwitterTailoredAudience.objects.filter( tw_targeting_id=targeting.tw_targeting_id).first() if audience: if not targeting.targeting_params: # for fallback, since all app install tailored audiences' targeting_params is empty _type = 'Excluded' else: if 'EXCLUDED' in targeting.targeting_params: _type = 'Excluded' else: _type = 'Included' res[targeting.name].append("<b>%s</b>: %s" % (_type, audience.name)) return res
class AdSerializer(BaseModelSerializer): # pylint: disable=old-style-class ad_group_id = PKRelatedField(queryset=AdGroup.objects_raw.all()) ad_group = serializers.CharField(read_only=True) campaign_id = serializers.IntegerField(required=False, read_only=True) campaign = serializers.CharField(read_only=True) advertiser_id = serializers.IntegerField(required=False, read_only=True) advertiser = serializers.CharField(read_only=True) agency_id = serializers.IntegerField(read_only=True) agency = serializers.CharField(read_only=True) trading_desk_id = serializers.IntegerField(read_only=True) trading_desk = serializers.CharField(read_only=True) ad_type = serializers.IntegerField(max_value=256, min_value=0, required=True) encrypted_ad_id = serializers.CharField(required=False, read_only=True) bid = serializers.DecimalField(max_digits=9, decimal_places=6, required=False) status = ChoiceCaseInsensitiveField(choices=STATUS_CHOICES, required=True) inflator_text = InflatorTextField(required=False, allow_blank=False, default='* 1.00') last_update = DateTimeField(read_only=True) created_time = DateTimeField(read_only=True) targeting = serializers.CharField(required=False, validators=[BaseValidator.JSONValidator], allow_blank=True) class Meta: model = Ad validators = [ serializers.UniqueTogetherValidator( queryset=Ad.objects_raw.all(), fields=('ad_group_id', 'ad') ) ] def is_valid(self, *args, **kwargs): errors = {} valid = super(AdSerializer, self).is_valid(*args, **kwargs) raise_exception = kwargs.pop('raise_exception', True) ad_type = self.validated_data.get('ad_type', None) attrs = self.validated_data.get('attrs', None) i_url = self.validated_data.get('i_url', None) redirect_url = self.validated_data.get('redirect_url', None) ad_group = self.validated_data.get('ad_group_id') size = self.validated_data.get('size', None) bid = self.validated_data.get('bid', None) status = self.validated_data.get('status') external_args = self.validated_data.get('external_args', '') html = self.validated_data.get('html', '') validate_only = False request = self.context.get('request', None) if request and 'validate_only' in request.query_params: validate_only = True if size and size == '375x50': errors['size'] = '375x50 size is currently unsupported' if ad_group.revmap_rev_type == "impression" and bid is None: errors['bid'] = 'This field is requeired' if not BaseValidator.is_digit_or_none_value(ad_type): errors['ad_type'] = 'Field ad_type should be numbers' if int(ad_type) not in [choice[0] for choice in AD_TYPE_CHOICES]: errors['ad_type'] = 'Invalid ad_type' if int(ad_type) == AD_TYPE_MRAID or int(ad_type) == AD_TYPE_NATIVE: if not html: errors['html'] = 'HTML should not be empty' if ad_type is not None and int(ad_type) == AD_TYPE_VIDEO: if attrs is None or \ (CREATIVE_ATTRS_VIDEO_AUTO_PLAY not in [int(x) for x in attrs.split(' ') if x and BaseValidator.is_digit_or_none_value(x)]): errors['attrs'] = 'For VIDEO ad type creative attributes ' \ 'should contain 6 (In-Banner Video Ad (Auto Play))' if ad_type is not None and (int(ad_type) == AD_TYPE_MRAID or int(ad_type) == AD_TYPE_RICHMEDIA): if i_url is None or not i_url: errors['i_url'] = 'For MRAID or RICHMEDIA types i_url field should not be empty' else: try: URLValidator().__call__(i_url) except ValidationError: errors['i_url'] = 'Incorrect i_url field format: %s' % i_url if ad_type is not None and int(ad_type) == AD_TYPE_NATIVE: if size != '0x0': errors['i_url'] = 'For NATIVE type the only "0x0" size is permitted' if redirect_url and redirect_url.find('https://') == -1: errors['redirect_url'] = 'Invalid value for click url: insecure URL' if html.find('http://') != -1: errors['html'] = 'Invalid value: insecure URL' if status == STATUS_ENABLED and not redirect_url and not ad_group.redirect_url and not ad_group.campaign_id.redirect_url: errors['redirect_url'] = 'Field is required. You could specify it on Campaign, Ad Group or Ad level' if int(ad_type) == AD_TYPE_MRAID: try: external_args_json = json.loads(external_args) mraid_type = external_args_json.get('mraid_type', None) except ValueError: errors['external_args'] = 'Invalid value' else: if mraid_type: attrs_list = [int(x) for x in attrs.split(' ') if x and BaseValidator.is_digit_or_none_value(x)] if mraid_type == 'MRAID Video' and (CREATIVE_ATTRS_VIDEO_AUTO_PLAY not in attrs_list): errors['attrs'] = 'For VIDEO ad type creative attributes ' \ 'should contain 6 (In-Banner Video Ad (Auto Play))' if mraid_type == 'MRAID Playable' and (CREATIVE_ATTRS_USER_INTERACTIVE not in attrs_list): errors['attrs'] = 'For MRAID Playable ad type creative attributes ' \ 'should contain 13 (User Interactive (e.g., Embedded Games))' if (mraid_type == 'Expandable Display' or mraid_type == 'Expandable Playable' or mraid_type == 'Expandable Video') and (CREATIVE_ATTRS_EXPANDABLE not in attrs_list): errors['attrs'] = 'For MRAID {0} ad type creative attributes ' \ 'should contain 4 (Expandable (User Initiated - Click))'.format(mraid_type) else: errors['external_args'] = "mraid_type and other required parameters should be specified. " \ "Beware, HTML will be updated according to them " \ "after Ad is re-saved via UI" if ad_type == AD_TYPE_NATIVE: headline = None try: external_args_json = json.loads(external_args) headline = external_args_json.get('headline', None) except ValueError: errors['external_args'] = 'Invalid value' else: if headline is None: errors['external_args'] = 'Headline is required' elif not headline: errors['external_args'] = 'Headline is invalid' if status == STATUS_ENABLED or status == STATUS_ENABLED and self.instance: external_args_json = None vast_media_file = None vast_duration = None if external_args.strip(): try: external_args_json = json.loads(external_args) except ValueError: errors['external_args'] = 'Invalid JSON' else: vast_media_file = external_args_json.get("vast_media_file", None) vast_duration = external_args_json.get("vast_duration", None) if ad_type == AD_TYPE_NONE: errors['ad_type'] = 'Invalid value' elif ad_type == AD_TYPE_VIDEO: if not vast_media_file or not vast_duration: errors['external_args'] = 'vast_media_file and vast_duration must be defined in external_args (VAST)' else: if vast_media_file or vast_duration: errors['external_args'] = 'vast_media_file and vast_duration should not be defined in external_args (VAST)' if ad_type == AD_TYPE_NATIVE and not external_args: errors['external_args'] = 'This filed is required (NATIVE)' if ad_type != AD_TYPE_RICHMEDIA and ad_type != AD_TYPE_MRAID and ad_type != AD_TYPE_NATIVE: result = re.search(r'\bsrc="(.*?)"', html) if not result: errors['html'] = 'Invalid value: No url' else: html_link = result.group(1) url_obj = urlparse(html_link) result = re.search(r'\.[^\/]+', url_obj.path) if not result: errors['html'] = 'Invalid value: no extension' if external_args.find('http://') != -1: errors['external_args'] = 'Invalid value: insecure URL' # if not redirect_url and not ad_group.redirect_url and not ad_group.campaign_id.redirect_url: # errors['redirect_url'] = 'Redirect_url must be defined at least on one of campaign, ad_group or ad levels' inflator_valid, error_message = InflatorTextField.is_valid(self.validated_data.get('inflator_text')) if not inflator_valid: errors['inflator_text'] = error_message if errors: if raise_exception: raise serializers.ValidationError(errors) else: self._errors = errors valid = False return valid def to_representation(self, instance, **kwargs): to_representation_dict = super(AdSerializer, self).to_representation(instance, **kwargs) user = REGISTRY.get('user', None) try: data = json.loads(to_representation_dict.get('targeting'), object_pairs_hook=OrderedDict) except: data = None else: fields = ["targeting.%s" % f for f in data.keys()] permitted_fields = user.get_permitted_instance_fields(instance=instance, action='read', fields=fields) for k in data.keys(): if "targeting.%s" % k not in permitted_fields: del data[k] to_representation_dict['targeting'] = '' if data is None else json.dumps(data) return to_representation_dict def create(self, validated_data): data_path_list = self.initial_data.get('data_path_list', None) if validated_data['ad_type'] == AD_TYPE_MRAID and data_path_list: for item in data_path_list: if settings.MNG_CDN is None: new_path, old_path = urlparse(item['new_path']).path[1:], urlparse(item['old_path']).path[1:] else: new_path, old_path = item['new_path'].replace(settings.MNG_CDN, ''), item['old_path'].replace( settings.MNG_CDN, '') Blobs._move_data(new_path, old_path) # BUG(AMMYM-3399): When creating a new Native Ad we should always set iURL. If user did not enter a valid iURL, # we should get it from external args "mainimage". if validated_data['ad_type'] == AD_TYPE_NATIVE and not validated_data.get('i_url'): try: external_args = json.loads(validated_data['external_args']) except (TypeError, ValueError): external_args = None if external_args and external_args.get('mainimage'): validated_data['i_url'] = external_args['mainimage'] return super(AdSerializer, self).create(validated_data) def update(self, instance, validated_data): data_path_list = self.initial_data.get('data_path_list', None) if validated_data['ad_type'] == AD_TYPE_MRAID and data_path_list: for item in data_path_list: if settings.MNG_CDN is None: new_path, old_path = urlparse(item['new_path']).path[1:], urlparse(item['old_path']).path[1:] else: new_path, old_path = item['new_path'].replace(settings.MNG_CDN, ''), item['old_path'].replace( settings.MNG_CDN, '') Blobs._move_data(new_path, old_path) return super(AdSerializer, self).update(instance, validated_data)
class CustomHintIdsSerializer(BaseBidderListIdsSerializer): ad_id = IntRelationField(required=False, related_model=Ad, allow_zero=True) start_date = WideRangeDateTimeField(required=False) end_date = WideRangeDateTimeField(required=True) last_update = DateTimeField(read_only=True) tag = serializers.CharField(required=False, allow_blank=True, allow_null=True) inflator_type = serializers.ChoiceField(choices=INFLATOR_TYPE_CHOICES, required=False, allow_null=True) size = serializers.ChoiceField(choices=CUSTOM_HINT_SIZE_CHOICES, required=False) inflator = serializers.DecimalField(max_digits=9, decimal_places=6, required=False, allow_null=True) priority = serializers.IntegerField(required=False) max_frequency = serializers.IntegerField(required=False) frequency_interval = serializers.IntegerField(required=False) # pylint: disable=old-style-class class Meta: model = CustomHintIds def is_valid(self, *args, **kwargs): errors_key = {} valid = super(CustomHintIdsSerializer, self).is_valid(*args, **kwargs) ad_id = self.validated_data.get('ad_id', None) campaign_id = self.validated_data.get('campaign_id', None) ad_group_id = self.validated_data.get('ad_group_id', None) if ad_id: ad_id = int(ad_id) # pylint: disable=invalid-name ad = Ad.objects_raw.get(pk=ad_id) true_ad_group = ad.ad_group_id true_ad_group_id = int(true_ad_group.pk) true_campaign = ad.ad_group_id.campaign_id true_campaign_id = int(true_campaign.pk) if ad_group_id: if true_ad_group_id != int(ad_group_id): message = 'Selected ad(%s/%s) and ad group(%s) do not match' % (ad_id, true_ad_group_id, ad_group_id) errors_key['ad_id, ad_group_id'] = message if campaign_id: if true_campaign_id != int(campaign_id): message = 'Selected ad(%s/%s) and campaign(%s) do not match' % (ad_id, true_campaign_id, campaign_id) errors_key['ad_id, campaign_id'] = message # inflator / inflator_type inflator = self.validated_data.get('inflator', None) inflator_type = self.validated_data.get('inflator_type', None) if inflator is not None and not inflator_type: errors_key['inflator_type'] = 'Field is required if "inflator" field contain data' elif inflator_type and not inflator and inflator != 0: errors_key['inflator'] = 'Field is required if "inflator_type" field contain data' # max_frequency / frequency_interval max_frequency = self.validated_data.get('max_frequency', None) start_date = self.validated_data.get('start_date', None) frequency_interval = self.validated_data.get('frequency_interval', None) if max_frequency and not frequency_interval: errors_key['frequency_interval'] = 'Field is required if "max_frequency" field contain data' elif frequency_interval and not max_frequency: errors_key['max_frequency'] = 'Field is required if "frequency_interval" field contain data' if max_frequency and not start_date: errors_key['start_date'] = 'Field is required if "max_frequency" field contain data' source_id = self.validated_data.get('source_id', None) if source_id is None or source_id == 0: errors_key['source_id'] = 'Field is required' if ad_group_id is not None and ad_group_id != 0: if campaign_id is None or campaign_id == 0: errors_key['campaign_id'] = 'Field is required' if errors_key: raise serializers.ValidationError(errors_key) return valid