Пример #1
0
    def POST_edit_campaign(self, form, jquery, l, indx, dates, bid, sr, targeting):
        if not l:
            return

        start, end = dates or (None, None)

        if start and end and not promote.is_accepted(l) and not c.user_is_sponsor:
            # if the ad is not approved already, ensure the start date
            # is at least 2 days in the future
            start = start.date()
            end = end.date()
            now = promote.promo_datetime_now()
            future = make_offset_date(now, g.min_promote_future, business_days=True)
            if start < future.date():
                c.errors.add(errors.BAD_FUTURE_DATE, msg_params=dict(day=g.min_promote_future), field="startdate")

        if form.has_errors("startdate", errors.BAD_DATE, errors.BAD_FUTURE_DATE) or form.has_errors(
            "enddate", errors.BAD_DATE, errors.BAD_FUTURE_DATE, errors.BAD_DATE_RANGE
        ):
            return

        duration = max((end - start).days, 1)

        if form.has_errors("bid", errors.BAD_BID):
            return

        if bid is None or float(bid) / duration < g.min_promote_bid:
            c.errors.add(errors.BAD_BID, field="bid", msg_params={"min": g.min_promote_bid, "max": g.max_promote_bid})
            form.has_errors("bid", errors.BAD_BID)
            return

        if targeting == "one":
            if form.has_errors("sr", errors.SUBREDDIT_NOEXIST, errors.SUBREDDIT_NOTALLOWED, errors.SUBREDDIT_REQUIRED):
                # checking to get the error set in the form, but we can't
                # check for rate-limiting if there's no subreddit
                return
            oversold = promote.is_roadblocked(sr.name, start, end)
            if oversold:
                c.errors.add(
                    errors.OVERSOLD,
                    field="sr",
                    msg_params={"start": oversold[0].strftime("%m/%d/%Y"), "end": oversold[1].strftime("%m/%d/%Y")},
                )
                form.has_errors("sr", errors.OVERSOLD)
                return
        if targeting == "none":
            sr = None

        if indx is not None:
            promote.edit_campaign(l, indx, dates, bid, sr)
            l = promote.editable_add_props(l)
            jquery.update_campaign(*l.campaigns[indx])
        else:
            indx = promote.new_campaign(l, dates, bid, sr)
            l = promote.editable_add_props(l)
            jquery.new_campaign(*l.campaigns[indx])
Пример #2
0
 def run(self, date):
     now = self.reference_date()
     override = c.user_is_sponsor and self.override
     try:
         date = datetime.strptime(date, "%m/%d/%Y")
         if not override:
             # can't put in __init__ since we need the date on the fly
             future = utils.make_offset_date(now, self.future,
                                       business_days = self.business_days)
             past = utils.make_offset_date(now, self.past, future = False,
                                       business_days = self.business_days)
             if self.future is not None and date.date() < future.date():
                 self.set_error(errors.BAD_FUTURE_DATE,
                            {"day": self.future})
             elif self.past is not None and date.date() > past.date():
                 self.set_error(errors.BAD_PAST_DATE,
                                {"day": self.past})
         return date.replace(tzinfo=g.tz)
     except (ValueError, TypeError):
         self.set_error(errors.BAD_DATE)
Пример #3
0
    def POST_edit_campaign(self, form, jquery, l, indx, dates, bid, sr,
                           targeting):
        if not l:
            return

        start, end = dates or (None, None)

        if start and end and not promote.is_accepted(
                l) and not c.user_is_sponsor:
            # if the ad is not approved already, ensure the start date
            # is at least 2 days in the future
            start = start.date()
            end = end.date()
            now = promote.promo_datetime_now()
            future = make_offset_date(now,
                                      g.min_promote_future,
                                      business_days=True)
            if start < future.date():
                c.errors.add(errors.BAD_FUTURE_DATE,
                             msg_params=dict(day=g.min_promote_future),
                             field="startdate")

        if (form.has_errors('startdate', errors.BAD_DATE,
                            errors.BAD_FUTURE_DATE)
                or form.has_errors('enddate', errors.BAD_DATE,
                                   errors.BAD_FUTURE_DATE,
                                   errors.BAD_DATE_RANGE)):
            return

        duration = max((end - start).days, 1)

        if form.has_errors('bid', errors.BAD_BID):
            return

        # minimum bid depends on user privilege and targeting, checked here
        # instead of in the validator b/c current duration is needed
        if c.user_is_admin:
            min_daily_bid = 0
        elif targeting == 'one':
            min_daily_bid = g.min_promote_bid * 1.5
        else:
            min_daily_bid = g.min_promote_bid

        if bid is None or bid / duration < min_daily_bid:
            c.errors.add(errors.BAD_BID,
                         field='bid',
                         msg_params={
                             'min': min_daily_bid,
                             'max': g.max_promote_bid
                         })
            form.has_errors('bid', errors.BAD_BID)
            return

        if targeting == 'one':
            if form.has_errors('sr', errors.SUBREDDIT_NOEXIST,
                               errors.SUBREDDIT_NOTALLOWED,
                               errors.SUBREDDIT_REQUIRED):
                # checking to get the error set in the form, but we can't
                # check for rate-limiting if there's no subreddit
                return
            oversold = promote.is_roadblocked(sr.name, start, end)
            if oversold and not c.user_is_sponsor:
                c.errors.add(errors.OVERSOLD,
                             field='sr',
                             msg_params={
                                 "start": oversold[0].strftime('%m/%d/%Y'),
                                 "end": oversold[1].strftime('%m/%d/%Y')
                             })
                form.has_errors('sr', errors.OVERSOLD)
                return
        if targeting == 'none':
            sr = None

        if indx is not None:
            promote.edit_campaign(l, indx, dates, bid, sr)
            l = promote.editable_add_props(l)
            jquery.update_campaign(*l.campaigns[indx])
        else:
            indx = promote.new_campaign(l, dates, bid, sr)
            l = promote.editable_add_props(l)
            jquery.new_campaign(*l.campaigns[indx])
Пример #4
0
    def POST_edit_campaign(self, form, jquery, l, campaign_id36,
                          dates, bid, sr, targeting):
        if not l:
            return

        start, end = dates or (None, None)

        if (start and end and not promote.is_accepted(l) and
            not c.user_is_sponsor):
            # if the ad is not approved already, ensure the start date
            # is at least 2 days in the future
            start = start.date()
            end = end.date()
            now = promote.promo_datetime_now()
            future = make_offset_date(now, g.min_promote_future,
                                      business_days=True)
            if start < future.date():
                c.errors.add(errors.BAD_FUTURE_DATE,
                             msg_params=dict(day=g.min_promote_future),
                             field="startdate")


        if (form.has_errors('startdate', errors.BAD_DATE,
                            errors.BAD_FUTURE_DATE) or
            form.has_errors('enddate', errors.BAD_DATE,
                            errors.BAD_FUTURE_DATE, errors.BAD_DATE_RANGE)):
            return

        # Limit the number of PromoCampaigns a Link can have
        # Note that the front end should prevent the user from getting
        # this far
        existing_campaigns = list(PromoCampaign._by_link(l._id))
        if len(existing_campaigns) > g.MAX_CAMPAIGNS_PER_LINK:
            c.errors.add(errors.TOO_MANY_CAMPAIGNS,
                         msg_params={'count': g.MAX_CAMPAIGNS_PER_LINK},
                         field='title')
            form.has_errors('title', errors.TOO_MANY_CAMPAIGNS)
            return

        duration = max((end - start).days, 1)

        if form.has_errors('bid', errors.BAD_BID):
            return

        # minimum bid depends on user privilege and targeting, checked here
        # instead of in the validator b/c current duration is needed
        if c.user_is_sponsor:
            min_daily_bid = 0
        elif targeting == 'one':
            min_daily_bid = g.min_promote_bid * 1.5
        else:
            min_daily_bid = g.min_promote_bid

        if campaign_id36:
            # you cannot edit the bid of a live ad unless it's a freebie
            try:
                campaign = PromoCampaign._byID36(campaign_id36)
                if (bid != campaign.bid and
                    campaign.start_date < datetime.now(g.tz)
                    and not campaign.is_freebie()):
                    c.errors.add(errors.BID_LIVE, field='bid')
                    form.has_errors('bid', errors.BID_LIVE)
                    return
            except NotFound:
                pass

        if bid is None or bid / duration < min_daily_bid:
            c.errors.add(errors.BAD_BID, field='bid',
                         msg_params={'min': min_daily_bid,
                                       'max': g.max_promote_bid})
            form.has_errors('bid', errors.BAD_BID)
            return

        if targeting == 'one':
            if form.has_errors('sr', errors.SUBREDDIT_NOEXIST,
                               errors.SUBREDDIT_NOTALLOWED,
                               errors.SUBREDDIT_REQUIRED):
                # checking to get the error set in the form, but we can't
                # check for rate-limiting if there's no subreddit
                return
            oversold = promote.is_roadblocked(sr.name, start, end)
            if oversold and not c.user_is_sponsor:
                msg_params = {"start": oversold[0].strftime('%m/%d/%Y'),
                              "end": oversold[1].strftime('%m/%d/%Y')}
                c.errors.add(errors.OVERSOLD, field='sr',
                             msg_params=msg_params)
                form.has_errors('sr', errors.OVERSOLD)
                return
        if targeting == 'none':
            sr = None

        if campaign_id36 is not None:
            campaign = PromoCampaign._byID36(campaign_id36)
            promote.edit_campaign(l, campaign, dates, bid, sr)
            r = promote.get_renderable_campaigns(l, campaign)
            jquery.update_campaign(r.campaign_id36, r.start_date, r.end_date,
                                   r.duration, r.bid, r.sr, r.status)
        else:
            campaign = promote.new_campaign(l, dates, bid, sr)
            r = promote.get_renderable_campaigns(l, campaign)
            jquery.new_campaign(r.campaign_id36, r.start_date, r.end_date,
                                r.duration, r.bid, r.sr, r.status)
Пример #5
0
    def POST_edit_campaign(self, form, jquery, l, indx,
                          dates, bid, sr, targeting):
        if not l:
            return
        
        start, end = dates or (None, None)

        if start and end and not promote.is_accepted(l) and not c.user_is_sponsor:
            # if the ad is not approved already, ensure the start date
            # is at least 2 days in the future
            start = start.date()
            end = end.date()
            now = promote.promo_datetime_now()
            future = make_offset_date(now, g.min_promote_future,
                                      business_days = True)
            if start < future.date():
                c.errors.add(errors.BAD_FUTURE_DATE,
                             msg_params = dict(day=g.min_promote_future),
                             field = "startdate")


        if (form.has_errors('startdate', errors.BAD_DATE,
                            errors.BAD_FUTURE_DATE) or
            form.has_errors('enddate', errors.BAD_DATE,
                            errors.BAD_FUTURE_DATE, errors.BAD_DATE_RANGE)):
            return

        duration = max((end - start).days, 1)

        if form.has_errors('bid', errors.BAD_BID):
            return

        # minimum bid depends on user privilege and targeting, checked here
        # instead of in the validator b/c current duration is needed
        if c.user_is_admin:
            min_daily_bid = 0
        elif targeting == 'one':
            min_daily_bid = g.min_promote_bid * 1.5
        else:
            min_daily_bid = g.min_promote_bid

        if bid is None or bid / duration < min_daily_bid:
            c.errors.add(errors.BAD_BID, field = 'bid',
                         msg_params = {'min': min_daily_bid,
                                       'max': g.max_promote_bid})
            form.has_errors('bid', errors.BAD_BID)
            return

        if targeting == 'one':
            if form.has_errors('sr', errors.SUBREDDIT_NOEXIST,
                               errors.SUBREDDIT_NOTALLOWED,
                               errors.SUBREDDIT_REQUIRED):
                # checking to get the error set in the form, but we can't
                # check for rate-limiting if there's no subreddit
                return
            oversold = promote.is_roadblocked(sr.name, start, end)
            if oversold and not c.user_is_sponsor:
                c.errors.add(errors.OVERSOLD, field = 'sr',
                             msg_params = {"start": oversold[0].strftime('%m/%d/%Y'),
                                           "end": oversold[1].strftime('%m/%d/%Y')})
                form.has_errors('sr', errors.OVERSOLD)
                return
        if targeting == 'none':
            sr = None

        if indx is not None:
            promote.edit_campaign(l, indx, dates, bid, sr)
            l = promote.editable_add_props(l)
            jquery.update_campaign(*l.campaigns[indx])
        else:
            indx = promote.new_campaign(l, dates, bid, sr)
            l = promote.editable_add_props(l)
            jquery.new_campaign(*l.campaigns[indx])
Пример #6
0
    def POST_edit_campaign(self, form, jquery, link, campaign_id36, dates, bid,
                           sr, targeting):
        if not link:
            return

        start, end = dates or (None, None)

        author = Account._byID(link.author_id, data=True)
        cpm = author.cpm_selfserve_pennies

        if (start and end and not promote.is_accepted(link)
                and not c.user_is_sponsor):
            # if the ad is not approved already, ensure the start date
            # is at least 2 days in the future
            start = start.date()
            end = end.date()
            now = promote.promo_datetime_now()
            future = make_offset_date(now,
                                      g.min_promote_future,
                                      business_days=True)
            if start < future.date():
                c.errors.add(errors.BAD_FUTURE_DATE,
                             msg_params=dict(day=g.min_promote_future),
                             field="startdate")

        if (form.has_errors('startdate', errors.BAD_DATE,
                            errors.BAD_FUTURE_DATE)
                or form.has_errors('enddate', errors.BAD_DATE,
                                   errors.BAD_FUTURE_DATE,
                                   errors.BAD_DATE_RANGE)):
            return

        # Limit the number of PromoCampaigns a Link can have
        # Note that the front end should prevent the user from getting
        # this far
        existing_campaigns = list(PromoCampaign._by_link(link._id))
        if len(existing_campaigns) > g.MAX_CAMPAIGNS_PER_LINK:
            c.errors.add(errors.TOO_MANY_CAMPAIGNS,
                         msg_params={'count': g.MAX_CAMPAIGNS_PER_LINK},
                         field='title')
            form.has_errors('title', errors.TOO_MANY_CAMPAIGNS)
            return

        if form.has_errors('bid', errors.BAD_BID):
            return

        if campaign_id36:
            # you cannot edit the bid of a live ad unless it's a freebie
            try:
                campaign = PromoCampaign._byID36(campaign_id36)
                if (bid != campaign.bid
                        and campaign.start_date < datetime.now(g.tz)
                        and not campaign.is_freebie()):
                    c.errors.add(errors.BID_LIVE, field='bid')
                    form.has_errors('bid', errors.BID_LIVE)
                    return
            except NotFound:
                pass

        min_bid = 0 if c.user_is_sponsor else g.min_promote_bid
        if bid is None or bid < min_bid:
            c.errors.add(errors.BAD_BID,
                         field='bid',
                         msg_params={
                             'min': min_bid,
                             'max': g.max_promote_bid
                         })
            form.has_errors('bid', errors.BAD_BID)
            return

        if targeting == 'one':
            if form.has_errors('sr', errors.SUBREDDIT_NOEXIST,
                               errors.SUBREDDIT_NOTALLOWED,
                               errors.SUBREDDIT_REQUIRED):
                # checking to get the error set in the form, but we can't
                # check for rate-limiting if there's no subreddit
                return

            roadblock = PromotedLinkRoadblock.is_roadblocked(sr, start, end)
            if roadblock and not c.user_is_sponsor:
                msg_params = {
                    "start": roadblock[0].strftime('%m/%d/%Y'),
                    "end": roadblock[1].strftime('%m/%d/%Y')
                }
                c.errors.add(errors.OVERSOLD,
                             field='sr',
                             msg_params=msg_params)
                form.has_errors('sr', errors.OVERSOLD)
                return

        elif targeting == 'none':
            sr = None

        # Check inventory
        campaign_id = campaign._id if campaign_id36 else None
        if has_oversold_error(form, campaign_id, start, end, bid, cpm, sr):
            return

        if campaign_id36 is not None:
            campaign = PromoCampaign._byID36(campaign_id36)
            promote.edit_campaign(link, campaign, dates, bid, cpm, sr)
            r = promote.get_renderable_campaigns(link, campaign)
            jquery.update_campaign(r.campaign_id36, r.start_date, r.end_date,
                                   r.duration, r.bid, r.spent, r.cpm, r.sr,
                                   r.status)
        else:
            campaign = promote.new_campaign(link, dates, bid, cpm, sr)
            r = promote.get_renderable_campaigns(link, campaign)
            jquery.new_campaign(r.campaign_id36, r.start_date, r.end_date,
                                r.duration, r.bid, r.spent, r.cpm, r.sr,
                                r.status)
Пример #7
0
    def POST_edit_campaign(self, form, jquery, link, campaign_id36,
                          dates, bid, sr, targeting, priority):
        if not link:
            return

        start, end = dates or (None, None)

        author = Account._byID(link.author_id, data=True)
        cpm = author.cpm_selfserve_pennies

        if (start and end and not promote.is_accepted(link) and
            not c.user_is_sponsor):
            # if the ad is not approved already, ensure the start date
            # is at least 2 days in the future
            start = start.date()
            end = end.date()
            now = promote.promo_datetime_now()
            future = make_offset_date(now, g.min_promote_future,
                                      business_days=True)
            if start < future.date():
                day = promote.promo_datetime_now(offset=g.min_promote_future)
                day = day.strftime("%m/%d/%Y")
                c.errors.add(errors.DATE_TOO_EARLY, msg_params=dict(day=day),
                             field="startdate")

        if (form.has_errors('startdate', errors.BAD_DATE,
                            errors.DATE_TOO_EARLY, errors.DATE_TOO_LATE) or
            form.has_errors('enddate', errors.BAD_DATE, errors.DATE_TOO_EARLY,
                            errors.DATE_TOO_LATE, errors.BAD_DATE_RANGE)):
            return

        # Limit the number of PromoCampaigns a Link can have
        # Note that the front end should prevent the user from getting
        # this far
        existing_campaigns = list(PromoCampaign._by_link(link._id))
        if len(existing_campaigns) > g.MAX_CAMPAIGNS_PER_LINK:
            c.errors.add(errors.TOO_MANY_CAMPAIGNS,
                         msg_params={'count': g.MAX_CAMPAIGNS_PER_LINK},
                         field='title')
            form.has_errors('title', errors.TOO_MANY_CAMPAIGNS)
            return

        campaign = None
        if campaign_id36:
            try:
                campaign = PromoCampaign._byID36(campaign_id36)
            except NotFound:
                pass

        if priority.cpm:
            if form.has_errors('bid', errors.BAD_BID):
                return

            # you cannot edit the bid of a live ad unless it's a freebie
            if (campaign and bid != campaign.bid and
                campaign.start_date < datetime.now(g.tz) and
                not campaign.is_freebie()):
                c.errors.add(errors.BID_LIVE, field='bid')
                form.has_errors('bid', errors.BID_LIVE)
                return

            min_bid = 0 if c.user_is_sponsor else g.min_promote_bid
            if bid is None or bid < min_bid:
                c.errors.add(errors.BAD_BID, field='bid',
                             msg_params={'min': min_bid,
                                         'max': g.max_promote_bid})
                form.has_errors('bid', errors.BAD_BID)
                return
        else:
            bid = 0.   # Set bid to 0 as dummy value

        if targeting == 'one':
            if form.has_errors('sr', errors.SUBREDDIT_NOEXIST,
                               errors.SUBREDDIT_NOTALLOWED,
                               errors.SUBREDDIT_REQUIRED):
                # checking to get the error set in the form, but we can't
                # check for rate-limiting if there's no subreddit
                return

            roadblock = PromotedLinkRoadblock.is_roadblocked(sr, start, end)
            if roadblock and not c.user_is_sponsor:
                msg_params = {"start": roadblock[0].strftime('%m/%d/%Y'),
                              "end": roadblock[1].strftime('%m/%d/%Y')}
                c.errors.add(errors.OVERSOLD, field='sr',
                             msg_params=msg_params)
                form.has_errors('sr', errors.OVERSOLD)
                return

        elif targeting == 'none':
            sr = None

        # Check inventory
        campaign = campaign if campaign_id36 else None
        if (not priority.inventory_override and
            has_oversold_error(form, campaign, start, end, bid, cpm, sr)):
            return

        if campaign:
            promote.edit_campaign(link, campaign, dates, bid, cpm, sr, priority)
            r = promote.get_renderable_campaigns(link, campaign)
            jquery.update_campaign(r.campaign_id36, r.start_date, r.end_date,
                                   r.duration, r.bid, r.spent, r.cpm, r.sr,
                                   r.priority_name, r.inventory_override,
                                   r.status)
        else:
            campaign = promote.new_campaign(link, dates, bid, cpm, sr, priority)
            r = promote.get_renderable_campaigns(link, campaign)
            jquery.new_campaign(r.campaign_id36, r.start_date, r.end_date,
                                r.duration, r.bid, r.spent, r.cpm, r.sr,
                                r.priority_name, r.inventory_override, r.status)
Пример #8
0
    def POST_edit_campaign(self, form, jquery, l, campaign_id36, dates, bid,
                           sr, targeting):
        if not l:
            return

        start, end = dates or (None, None)

        if start and end and not promote.is_accepted(
                l) and not c.user_is_sponsor:
            # if the ad is not approved already, ensure the start date
            # is at least 2 days in the future
            start = start.date()
            end = end.date()
            now = promote.promo_datetime_now()
            future = make_offset_date(now,
                                      g.min_promote_future,
                                      business_days=True)
            if start < future.date():
                c.errors.add(errors.BAD_FUTURE_DATE,
                             msg_params=dict(day=g.min_promote_future),
                             field="startdate")

        if (form.has_errors('startdate', errors.BAD_DATE,
                            errors.BAD_FUTURE_DATE)
                or form.has_errors('enddate', errors.BAD_DATE,
                                   errors.BAD_FUTURE_DATE,
                                   errors.BAD_DATE_RANGE)):
            return

        duration = max((end - start).days, 1)

        if form.has_errors('bid', errors.BAD_BID):
            return

        # minimum bid depends on user privilege and targeting, checked here
        # instead of in the validator b/c current duration is needed
        if c.user_is_sponsor:
            min_daily_bid = 0
        elif targeting == 'one':
            min_daily_bid = g.min_promote_bid * 1.5
        else:
            min_daily_bid = g.min_promote_bid

        # you cannot edit the bid of a live ad unless it's a freebie
        try:
            campaign = PromoCampaign._byID(campaign_id36)
            if (bid != campaign.bid
                    and campaign.start_date < datetime.now(g.tz)
                    and not campaign.is_freebie()):
                c.errors.add(errors.BID_LIVE, field='bid')
                form.has_errors('bid', errors.BID_LIVE)
                return
        except NotFound:
            pass

        if bid is None or bid / duration < min_daily_bid:
            c.errors.add(errors.BAD_BID,
                         field='bid',
                         msg_params={
                             'min': min_daily_bid,
                             'max': g.max_promote_bid
                         })
            form.has_errors('bid', errors.BAD_BID)
            return

        if targeting == 'one':
            if form.has_errors('sr', errors.SUBREDDIT_NOEXIST,
                               errors.SUBREDDIT_NOTALLOWED,
                               errors.SUBREDDIT_REQUIRED):
                # checking to get the error set in the form, but we can't
                # check for rate-limiting if there's no subreddit
                return
            oversold = promote.is_roadblocked(sr.name, start, end)
            if oversold and not c.user_is_sponsor:
                c.errors.add(errors.OVERSOLD,
                             field='sr',
                             msg_params={
                                 "start": oversold[0].strftime('%m/%d/%Y'),
                                 "end": oversold[1].strftime('%m/%d/%Y')
                             })
                form.has_errors('sr', errors.OVERSOLD)
                return
        if targeting == 'none':
            sr = None

        if campaign_id36 is not None:
            campaign = PromoCampaign._byID36(campaign_id36)
            promote.edit_campaign(l, campaign, dates, bid, sr)
            r = promote.get_renderable_campaigns(l, campaign)
            jquery.update_campaign(r.campaign_id36, r.start_date, r.end_date,
                                   r.duration, r.bid, r.sr, r.status)
        else:
            campaign = promote.new_campaign(l, dates, bid, sr)
            r = promote.get_renderable_campaigns(l, campaign)
            jquery.new_campaign(r.campaign_id36, r.start_date, r.end_date,
                                r.duration, r.bid, r.sr, r.status)
Пример #9
0
    def POST_edit_campaign(self, form, jquery, link, campaign_id36, dates, bid, sr, targeting):
        if not link:
            return

        start, end = dates or (None, None)

        author = Account._byID(link.author_id, data=True)
        cpm = author.cpm_selfserve_pennies

        if start and end and not promote.is_accepted(link) and not c.user_is_sponsor:
            # if the ad is not approved already, ensure the start date
            # is at least 2 days in the future
            start = start.date()
            end = end.date()
            now = promote.promo_datetime_now()
            future = make_offset_date(now, g.min_promote_future, business_days=True)
            if start < future.date():
                c.errors.add(errors.BAD_FUTURE_DATE, msg_params=dict(day=g.min_promote_future), field="startdate")

        if form.has_errors("startdate", errors.BAD_DATE, errors.BAD_FUTURE_DATE) or form.has_errors(
            "enddate", errors.BAD_DATE, errors.BAD_FUTURE_DATE, errors.BAD_DATE_RANGE
        ):
            return

        # Limit the number of PromoCampaigns a Link can have
        # Note that the front end should prevent the user from getting
        # this far
        existing_campaigns = list(PromoCampaign._by_link(link._id))
        if len(existing_campaigns) > g.MAX_CAMPAIGNS_PER_LINK:
            c.errors.add(errors.TOO_MANY_CAMPAIGNS, msg_params={"count": g.MAX_CAMPAIGNS_PER_LINK}, field="title")
            form.has_errors("title", errors.TOO_MANY_CAMPAIGNS)
            return

        if form.has_errors("bid", errors.BAD_BID):
            return

        if campaign_id36:
            # you cannot edit the bid of a live ad unless it's a freebie
            try:
                campaign = PromoCampaign._byID36(campaign_id36)
                if bid != campaign.bid and campaign.start_date < datetime.now(g.tz) and not campaign.is_freebie():
                    c.errors.add(errors.BID_LIVE, field="bid")
                    form.has_errors("bid", errors.BID_LIVE)
                    return
            except NotFound:
                pass

        min_bid = 0 if c.user_is_sponsor else g.min_promote_bid
        if bid is None or bid < min_bid:
            c.errors.add(errors.BAD_BID, field="bid", msg_params={"min": min_bid, "max": g.max_promote_bid})
            form.has_errors("bid", errors.BAD_BID)
            return

        if targeting == "one":
            if form.has_errors("sr", errors.SUBREDDIT_NOEXIST, errors.SUBREDDIT_NOTALLOWED, errors.SUBREDDIT_REQUIRED):
                # checking to get the error set in the form, but we can't
                # check for rate-limiting if there's no subreddit
                return

            roadblock = PromotedLinkRoadblock.is_roadblocked(sr, start, end)
            if roadblock and not c.user_is_sponsor:
                msg_params = {"start": roadblock[0].strftime("%m/%d/%Y"), "end": roadblock[1].strftime("%m/%d/%Y")}
                c.errors.add(errors.OVERSOLD, field="sr", msg_params=msg_params)
                form.has_errors("sr", errors.OVERSOLD)
                return

        elif targeting == "none":
            sr = None

        # Check inventory
        campaign_id = campaign._id if campaign_id36 else None
        if has_oversold_error(form, campaign_id, start, end, bid, cpm, sr):
            return

        if campaign_id36 is not None:
            campaign = PromoCampaign._byID36(campaign_id36)
            promote.edit_campaign(link, campaign, dates, bid, cpm, sr)
            r = promote.get_renderable_campaigns(link, campaign)
            jquery.update_campaign(
                r.campaign_id36, r.start_date, r.end_date, r.duration, r.bid, r.spent, r.cpm, r.sr, r.status
            )
        else:
            campaign = promote.new_campaign(link, dates, bid, cpm, sr)
            r = promote.get_renderable_campaigns(link, campaign)
            jquery.new_campaign(
                r.campaign_id36, r.start_date, r.end_date, r.duration, r.bid, r.spent, r.cpm, r.sr, r.status
            )