Пример #1
0
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
Пример #4
0
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())
Пример #5
0
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 {}
Пример #6
0
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'))
        ]
Пример #9
0
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
Пример #12
0
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 ''
Пример #20
0
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
Пример #21
0
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
Пример #23
0
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