Ejemplo n.º 1
0
    def post(self):
        try:
            # verify user can write the data, otherwise abort (adapted from loutilities.tables._editormethod)
            if not self.permission():
                db.session.rollback()
                cause = 'operation not permitted for user'
                return jsonify(error=cause)

            # there should be one 'id' in this form data, 'keyless'
            requestdata = get_request_data(request.form)
            motion_id = request.args['motion_id']
            from_email = requestdata['keyless']['from_email']
            subject = requestdata['keyless']['subject']
            message = requestdata['keyless']['message']

            generateevotes(motion_id, from_email, subject, message)

            self._responsedata = []

            db.session.commit()
            return jsonify(self._responsedata)

        except Exception as e:
            exc = ''.join(format_exception_only(type(e), e))
            output_result = {'status' : 'fail', 'error': 'exception occurred:<br>{}'.format(exc)}
            # roll back database updates and close transaction
            db.session.rollback()
            current_app.logger.error(format_exc())
            return jsonify(output_result)
Ejemplo n.º 2
0
    def _validate(self, action, formdata):
        results = []

        # kludge to get task.id
        # NOTE: this is only called from 'edit' / put function, and there will be only one id
        thisid = list(get_request_data(request.form).keys())[0]
        thistask = Task.query.filter_by(id=thisid).one()

        # build lists of required and shared fields
        required = []
        one_of = []
        override_completion = []
        for tasktaskfield in thistask.fields:
            taskfield = tasktaskfield.taskfield
            # ignore display-only fields
            if taskfield.inputtype == INPUT_TYPE_DISPLAY:
                continue
            if tasktaskfield.need == NEED_REQUIRED:
                required.append(taskfield.fieldname)
            elif tasktaskfield.need == NEED_ONE_OF:
                one_of.append(taskfield.fieldname)
            if taskfield.override_completion:
                override_completion.append(taskfield.fieldname)

        # verify required fields were supplied
        for field in required:
            if not formdata[field]:
                results.append({'name': field, 'status': 'please supply'})

        # verify one of the one_of fields was supplied
        onefound = False
        for field in one_of:
            if formdata[field]:
                onefound = True
        if not onefound:
            for field in one_of:
                results.append({'name':field, 'status': 'one of these must be supplied'})

        # verify fields which override completion date (should only be one if configured properly)
        for field in override_completion:
            if formdata[field] > date.today().isoformat():
                results.append({'name':field, 'status': 'cannot specify date later than today'})

        return results
Ejemplo n.º 3
0
    def editor_method_posthook(self, form):
        #----------------------------------------------------------------------
        '''
        send contract to client contact if asked to do so, after processing put()

        note row has already been committed to the database, so can be retrieved
        '''
        # the following can be true only for put() [edit] method
        if 'addlaction' in form and form['addlaction'] in [
                'sendcontract', 'resendcontract'
        ]:
            folderid = current_app.config['CONTRACTS_DB_FOLDER']

            # need an instance of contract manager to take care of saving the contract
            cm = ContractManager(contractType='race services',
                                 templateType='contract',
                                 driveFolderId=folderid)

            # pull record(s) from database and save as flat dotted record
            data = get_request_data(form)
            print(('data={}'.format(data)))
            for thisid in data:
                eventdb = Event.query.filter_by(id=thisid).one()

                # different subject line if contract had been accepted before. This must match contractviews.AcceptAgreement.post
                annotation = ''

                # if we are generating a new version of the contract
                if form['addlaction'] == 'sendcontract':
                    # if there was already a document sent, indicate that we're updating it
                    if eventdb.contractDocId:
                        eventdb.isContractUpdated = True
                        annotation = '(updated) '

                    # check appropriate fields are present for certain services
                    servicenames = {s.service for s in eventdb.services}
                    if servicenames & {'coursemarking', 'finishline'}:
                        self._fielderrors = []
                        for field in [
                                'race', 'date', 'mainStartTime', 'mainDistance'
                        ]:
                            if not data[thisid][field]:
                                self._fielderrors.append({
                                    'name':
                                    field,
                                    'status':
                                    'please supply'
                                })
                        ## handle select fields
                        for field in [
                                'state', 'services', 'client', 'course', 'lead'
                        ]:
                            if not data[thisid][field]['id']:
                                self._fielderrors.append({
                                    'name':
                                    '{}.id'.format(field),
                                    'status':
                                    'please select'
                                })
                        if self._fielderrors:
                            raise parameterError('missing fields')

                    # calculate service fees
                    servicefees = []

                    feetotal = 0
                    for service in eventdb.services:
                        servicefee = {'service': service.serviceLong}
                        # fixed fee
                        if service.feeType.feeType == 'fixed':
                            thisfee = service.fee
                            servicefee['fee'] = thisfee
                            servicefees.append(servicefee)

                        # fee is based on another field
                        elif service.feeType.feeType == 'basedOnField':
                            field = service.basedOnField
                            # not clear why this needs to be converted to int, but otherwise see unicode value
                            # if can't be converted, then invalid format
                            try:
                                fieldval = int(getattr(eventdb, field))
                            except (TypeError, ValueError) as e:
                                fieldval = None

                            # field not set, then set self._fielderrors appropriately
                            if not fieldval:
                                formfield = self.dbmapping[
                                    field]  # hopefully not a function
                                self._fielderrors = [{
                                    'name':
                                    formfield,
                                    'status':
                                    'needed to calculate fee'
                                }]
                                raise parameterError(
                                    'cannot calculate fee if {} not set'.
                                    format(field))

                            feebasedons = FeeBasedOn.query.filter_by(
                                serviceId=service.id).order_by(
                                    FeeBasedOn.fieldValue).all()
                            foundfee = False
                            for feebasedon in feebasedons:
                                lastfieldval = feebasedon.fieldValue
                                if debug:
                                    current_app.logger.debug(
                                        'fieldval={} feebasedon.fieldValue={}'.
                                        format(fieldval,
                                               feebasedon.fieldValue))
                                if debug:
                                    current_app.logger.debug(
                                        'type(fieldval)={} type(feebasedon.fieldValue)={}'
                                        .format(type(fieldval),
                                                type(feebasedon.fieldValue)))
                                if fieldval <= feebasedon.fieldValue:
                                    thisfee = feebasedon.fee
                                    servicefee['fee'] = thisfee
                                    servicefees.append(servicefee)
                                    foundfee = True
                                    break

                            # if fee not found, then set fielderrors appropriately
                            if not foundfee:
                                formfield = self.dbmapping[
                                    field]  # hopefully not a function
                                self._fielderrors = [{
                                    'name':
                                    formfield,
                                    'status':
                                    'cannot calculate fee if this is greater than {}'
                                    .format(lastfieldval)
                                }]
                                raise parameterError(
                                    'cannot calculate fee if {} greater than {}'
                                    .format(field, lastfieldval))

                        # not sure how we could get here, but best to be defensive
                        else:
                            raise parameterError('unknown feeType: {}'.format(
                                service.feeType.feeType))

                        # accumulate total fee
                        feetotal += thisfee

                    # need to calculate addons in addition to services
                    for addon in eventdb.addOns:
                        thisfee = addon.fee
                        servicefee = {
                            'service': addon.longDescr,
                            'fee': thisfee
                        }
                        servicefees.append(servicefee)

                        # accumulate total fee
                        feetotal += thisfee

                    # generate contract
                    if debug:
                        current_app.logger.debug(
                            'editor_method_posthook(): (before create()) eventdb.__dict__={}'
                            .format(eventdb.__dict__))
                    docid = cm.create(
                        '{}-{}-{}.docx'.format(eventdb.client.client,
                                               eventdb.race.race,
                                               eventdb.date),
                        eventdb,
                        addlfields={
                            'servicenames':
                            [s.service for s in eventdb.services],
                            'servicefees': servicefees,
                            'event': eventdb.race.race,
                            'totalfees': {
                                'service': 'TOTAL',
                                'fee': feetotal
                            },
                        })

                    # update database to show contract sent
                    eventdb.state = State.query.filter_by(
                        state=STATE_CONTRACT_SENT).one()
                    eventdb.contractSentDate = dt.dt2asc(date.today())
                    eventdb.contractDocId = docid

                    # find index with correct id and show database updates
                    for resprow in self._responsedata:
                        if resprow['rowid'] == thisid:
                            resprow['state'] = {
                                key: val
                                for (key, val
                                     ) in list(eventdb.state.__dict__.items())
                                if key[0] != '_'
                            }
                            resprow[
                                'contractSentDate'] = eventdb.contractSentDate
                            resprow['contractDocId'] = eventdb.contractDocId

                # if we are just resending current version of the contract
                else:
                    docid = eventdb.contractDocId
                    annotation = '(resend) '

                # email sent depends on current state as this flows from 'sendcontract' and 'resendcontract'
                if eventdb.state.state == STATE_COMMITTED:
                    # prepare agreement accepted email
                    templatestr = (db.session.query(Contract).filter(
                        Contract.contractTypeId == ContractType.id).filter(
                            ContractType.contractType == 'race services'
                        ).filter(
                            Contract.templateTypeId == TemplateType.id).filter(
                                TemplateType.templateType ==
                                'agreement accepted view').one()).block
                    template = Template(templatestr)
                    subject = '{}ACCEPTED - FSRC Race Support Agreement: {} - {}'.format(
                        annotation, eventdb.race.race, eventdb.date)

                elif eventdb.state.state == STATE_CONTRACT_SENT:
                    # send contract mail to client
                    templatestr = (db.session.query(Contract).filter(
                        Contract.contractTypeId == ContractType.id).filter(
                            ContractType.contractType == 'race services'
                        ).filter(
                            Contract.templateTypeId == TemplateType.id).filter(
                                TemplateType.templateType ==
                                'contract email').one()).block
                    template = Template(templatestr)
                    subject = '{}FSRC Race Support Agreement: {} - {}'.format(
                        annotation, eventdb.race.race, eventdb.date)

                # state must be STATE_COMMITTED or STATE_CONTRACT_SENT, else logic error
                else:
                    raise parameterError(
                        'editor_method_posthook(): bad state seen for {}: {}'.
                        format(form['addlaction'], eventdb.state.state))

                # merge database fields into template and send email
                mergefields = deepcopy(eventdb.__dict__)
                mergefields[
                    'viewcontracturl'] = 'https://docs.google.com/document/d/{}/view'.format(
                        docid)
                mergefields[
                    'downloadcontracturl'] = 'https://docs.google.com/document/d/{}/export?format=pdf'.format(
                        docid)
                # need to bring in full path for email, so use url_root
                mergefields[
                    'acceptcontracturl'] = request.url_root[:-1] + url_for(
                        'frontend.acceptagreement', docid=docid)
                mergefields['servicenames'] = [
                    s.service for s in eventdb.services
                ]
                mergefields['event'] = eventdb.race.race

                html = template.render(mergefields)
                tolist = eventdb.client.contactEmail
                cclist = current_app.config['CONTRACTS_CC']
                fromlist = current_app.config['CONTRACTS_CONTACT']
                sendmail(subject, fromlist, tolist, html, ccaddr=cclist)
Ejemplo n.º 4
0
    def post(self):
        try:
            requestdata = get_request_data(request.form)
            # verify user can write the data, otherwise abort (adapted from loutilities.tables._editormethod)
            # there should be one 'id' in this form data, 'keyless'
            if not self.permission(requestdata['keyless']['position_id']):
                db.session.rollback()
                cause = 'operation not permitted for user'
                return jsonify(error=cause)

            # get current members who previously held position on effective date
            effectivedate = requestdata['keyless']['effective']
            effectivedatedt = dtrender.asc2dt(effectivedate).date()
            currmembers = members_active(self.position, effectivedate)

            # there may be a qualifier for this position, e.g., "interim";
            # normally blank so set to None if so for backwards compatibility with the database after
            # initial conversion to include qualifier
            qualifier = requestdata['keyless']['qualifier']
            if not qualifier:
                qualifier = None

            # get the members which admin wants to be in the position on the effective date
            # separator must match afterdatatables.js else if (location.pathname.includes('/positions'))
            # (if empty string is returned, there were no memberids, so use empty list)
            resultmemberids = []
            if requestdata['keyless']['members']:
                resultmemberids = requestdata['keyless']['members'].split(', ')
            resultmembers = [
                LocalUser.query.filter_by(id=id).one()
                for id in resultmemberids
            ]

            # terminate all future user/positions in this position as we're basing our update on effectivedate assertion by admin
            # delete all of these which are strictly in the future
            currfuturemembers = members_active_currfuture(
                self.position, onorafter=effectivedate)
            for member in currfuturemembers:
                ups = member_positions(member,
                                       self.position,
                                       onorafter=effectivedate)
                for up in ups:
                    if up.startdate > effectivedatedt:
                        current_app.logger.debug(
                            f'organization_admin.PositionWizardApi.post: deleting {up.user.name} {up.position.position} {up.startdate}'
                        )
                        db.session.delete(up)
            db.session.flush()

            # get current members who previously held position on effective date
            currmembers = members_active(self.position, effectivedate)

            # terminate all current members in this position who should not remain in the result set
            # use date one day before effective date for new finish date
            previousdatedt = dtrender.asc2dt(effectivedate).date() - timedelta(
                1)
            for currmember in currmembers:
                ups = member_position_active(currmember, self.position,
                                             effectivedate)
                # more than one returned implies data error, needs to be fixed externally
                if len(ups) > 1:
                    db.session.rollback()
                    cause = 'existing position "{}" date overlap detected for {} on {}. Use ' \
                            '<a href="{}" target=_blank>Position Dates view</a> ' \
                            'to fix before proceeding'.format(self.position.position, currmember.name, effectivedate,
                                                                page_url_for('admin.positiondates', interest=g.interest))
                    return jsonify(error=cause)

                # also if none were returned there is some logic error, should not happen because currmembers pulled
                # in current records
                if not ups:
                    db.session.rollback()
                    cause = f'logic error: {currmember.name} not found for {self.position.position} on {effectivedate}. Please report to administrator'
                    return jsonify(error=cause)

                currup = ups[0]

                # if the current member isn't one of the members in the position starting effective date,
                if currmember not in resultmembers:
                    # overwrite finishdate -- maybe this was empty or maybe it had a date in it, either way now finished
                    # day before effective date
                    currup.finishdate = previousdatedt

                    # if the finish date is now before the start date, we can delete this record, to be tidy
                    if currup.finishdate < currup.startdate:
                        db.session.delete(currup)
                        db.session.flush()

            # loop through all members who are to be in the position as of effective date
            for resultmember in resultmembers:
                # check user/positions for this member on or after effective date
                ups = member_positions(resultmember,
                                       self.position,
                                       onorafter=effectivedate)

                # create new record for all resultmembers not already in the position
                # if the new member has a future record, move date the start of the future record to the effective date
                if resultmember not in currmembers:
                    # normal case is no future records, so create a new record as of effectivedate
                    if len(ups) == 0:
                        thisups = UserPosition(
                            interest=localinterest(),
                            user=resultmember,
                            position=self.position,
                            startdate=effectivedatedt,
                            qualifier=qualifier,
                        )
                        db.session.add(thisups)

                # if resultmember is in currmembers, but the qualifier has changed
                # NOTE: ups[0] is the user/position which is active on the effective date
                elif ups and ups[0].qualifier != qualifier:
                    currup = ups[0]
                    if len(ups) > 1:
                        # logic prevents use of futureup if not defined
                        futureup = ups[1]

                    # overwrite finishdate -- maybe this was empty or maybe it had a date in it, either way now finished
                    # day before effective date
                    finishdate = currup.finishdate
                    currup.finishdate = previousdatedt

                    # if the finish date is now before the start date, we can delete this record, to be tidy
                    if currup.finishdate < currup.startdate:
                        db.session.delete(currup)
                        db.session.flush()

                    # normal case there's only one current or future user/position, so create new one to follow this one
                    if len(ups) == 1:
                        thisups = UserPosition(
                            interest=localinterest(),
                            user=resultmember,
                            position=self.position,
                            startdate=effectivedatedt,
                            finishdate=None,
                            qualifier=qualifier,
                        )
                        db.session.add(thisups)

                    # if there's a future user/position, and it was right after the current, the qualifier has most likely changed
                    # assuming the qualifer of the future user/position is the new qualifier, move the start date of the future record
                    # NOTE: there should be no future records at this point, so this clause should not get executed
                    elif futureup.qualifier == qualifier and futureup.startdate == finishdate + timedelta(
                            1):
                        futureup.startdate = previousdatedt + timedelta(1)

            # commit all the changes and return success
            # NOTE: in afterdatatables.js else if (location.pathname.includes('/positions'))
            # table is redrawn on submitComplete in case this action caused visible changes
            output_result = {'status': 'success'}
            db.session.commit()
            return jsonify(output_result)

        except Exception as e:
            exc = ''.join(format_exception_only(type(e), e))
            output_result = {
                'status': 'fail',
                'error': 'exception occurred:\n{}'.format(exc)
            }
            # roll back database updates and close transaction
            db.session.rollback()
            current_app.logger.error(format_exc())
            return jsonify(output_result)
Ejemplo n.º 5
0
    def editor_method_posthook(self, form):
        #----------------------------------------------------------------------
        '''
        send contract to client contact if asked to do so, after processing put()

        note row has already been committed to the database, so can be retrieved
        '''

        # someday we might allow multiple records to be processed in a single request

        # pull record(s) from database and save as flat dotted record
        data = get_request_data(form)
        for thisid in data:
            sponsordb = Sponsor.query.filter_by(id=thisid).one_or_none()

            # if we're creating, we just flushed the row, but the id in the form was 0
            # retrieve the created row through saved id
            if not sponsordb:
                thisid = self.created_id
                sponsordb = Sponsor.query.filter_by(id=thisid).one()

            # the following can be true only for put() [edit] method
            if 'addlaction' in form and form['addlaction'] in [
                    'sendcontract', 'resendcontract'
            ]:
                folderid = current_app.config['CONTRACTS_DB_FOLDER']

                # need an instance of contract manager to take care of saving the contract
                cm = ContractManager(
                    contractType='race sponsorship',
                    templateType='sponsor agreement',
                    driveFolderId=folderid,
                    doctype='html',
                )

                racedate = SponsorRaceDate.query.filter_by(
                    race_id=sponsordb.race.id,
                    raceyear=sponsordb.raceyear).one()

                # bring in subrecords
                garbage = sponsordb.race
                garbage = sponsordb.client
                garbage = sponsordb.level

                # calculate the benefits (see https://stackoverflow.com/questions/40699642/how-to-query-many-to-many-sqlalchemy)
                benefitsdb = SponsorBenefit.query.join(
                    SponsorBenefit.levels).filter(
                        SponsorLevel.id == sponsordb.level.id).order_by(
                            SponsorBenefit.order).all()
                benefits = [b.benefit for b in benefitsdb]

                # calculate display for coupon count. word (num) if less than 10, otherwise num
                # but note there may not be a coupon count
                # ccouponcount is capitalized
                ncoupons = sponsordb.level.couponcount
                if ncoupons:
                    if ncoupons < 10:
                        wcoupons = 'zero one two three four five six seven eight nine'.split(
                        )[ncoupons]
                        couponcount = '{} ({})'.format(
                            wcoupons, ncoupons) if ncoupons else None
                    else:
                        couponcount = str(ncoupons)
                    ccouponcount = couponcount.capitalize()
                else:
                    couponcount = None
                    ccouponcount = None

                # pick up variables
                variablesdb = SponsorRaceVbl.query.filter_by(
                    race_id=sponsordb.race.id).all()
                variables = {v.variable: v.value for v in variablesdb}

                # if we are generating a new version of the contract
                if form['addlaction'] == 'sendcontract':

                    # set up dateagreed, if not already there
                    if not sponsordb.dateagreed:
                        sponsordb.dateagreed = dt.dt2asc(date.today())

                    # additional fields for contract
                    addlfields = {
                        '_date_':
                        humandt.dt2asc(dt.asc2dt(sponsordb.dateagreed)),
                        '_racedate_':
                        humandt.dt2asc(dt.asc2dt(racedate.racedate)),
                        '_rdcertlogo_':
                        pathjoin(current_app.static_folder,
                                 'rd-cert-logo.png'),
                        '_raceheader_':
                        '<img src="{}" width=6in>'.format(
                            pathjoin(
                                current_app.static_folder,
                                '{}-header.png'.format(
                                    sponsordb.race.raceshort.lower()))),
                        '_benefits_':
                        benefits,
                        '_raceloc_':
                        racedate.raceloc,
                        '_racebeneficiary_':
                        racedate.beneficiary,
                        '_couponcount_':
                        ccouponcount,  # ok to assume this is first word in sentence
                    }
                    addlfields.update(variables)

                    # generate contract
                    if debug:
                        current_app.logger.debug(
                            'editor_method_posthook(): (before create()) sponsordb.__dict__={}'
                            .format(sponsordb.__dict__))
                    docid = cm.create(
                        '{} {} {} Sponsor Agreement'.format(
                            sponsordb.raceyear, sponsordb.race.raceshort,
                            sponsordb.client.client),
                        sponsordb,
                        addlfields=addlfields,
                    )

                    # update database to show contract sent/agreed
                    sponsordb.state = State.query.filter_by(
                        state=STATE_COMMITTED).one()
                    sponsordb.contractDocId = docid

                    # find index with correct id and show database updates
                    for resprow in self._responsedata:
                        if resprow['rowid'] == thisid:
                            resprow['state'] = {
                                key: val
                                for (key, val) in list(
                                    sponsordb.state.__dict__.items())
                                if key[0] != '_'
                            }
                            resprow['dateagreed'] = sponsordb.dateagreed
                            resprow['contractDocId'] = sponsordb.contractDocId

                    # configure coupon provider with coupon code (supported providers)
                    if sponsordb.race.couponprovider and sponsordb.level.couponcount and sponsordb.level.couponcount > 0:
                        expiration = racedate.racedate
                        numregistrations = sponsordb.level.couponcount
                        clientname = sponsordb.client.client
                        raceid = sponsordb.race.couponproviderid
                        couponcode = sponsordb.couponcode
                        start = sponsordb.dateagreed
                        if sponsordb.race.couponprovider.lower(
                        ) == 'runsignup':
                            with RunSignUp(
                                    key=current_app.config['RSU_KEY'],
                                    secret=current_app.config['RSU_SECRET'],
                                    debug=debug) as rsu:
                                coupons = rsu.getcoupons(raceid, couponcode)
                                # rsu search includes any coupons with the couponcode with the coupon string, so we need to filter
                                coupons = [
                                    c for c in coupons
                                    if c['coupon_code'] == couponcode
                                ]
                                if coupons:
                                    coupon = coupons[
                                        -1]  # should be only one entry, but last is the current one (?)
                                    coupon_id = coupon['coupon_id']
                                    # override start with the date portion of start_date
                                    start = coupon['start_date'].split(' ')[0]
                                else:
                                    coupon_id = None
                                rsu.setcoupon(raceid,
                                              couponcode,
                                              start,
                                              expiration,
                                              numregistrations,
                                              clientname,
                                              coupon_id=coupon_id)

                # if we are just resending current version of the contract
                else:
                    docid = sponsordb.contractDocId

                # prepare agreement email (new contract or resending)
                templatestr = (db.session.query(Contract).filter(
                    Contract.contractTypeId == ContractType.id).filter(
                        ContractType.contractType ==
                        'race sponsorship').filter(
                            Contract.templateTypeId == TemplateType.id).filter(
                                TemplateType.templateType ==
                                'sponsor email').one()).block
                template = Template(templatestr)
                subject = '{} Sponsorship Agreement for {}'.format(
                    sponsordb.race.race, sponsordb.client.client)

                # bring in subrecords
                garbage = sponsordb.race

                # merge database fields into template and send email
                mergefields = deepcopy(sponsordb.__dict__)
                mergefields[
                    'viewcontracturl'] = 'https://docs.google.com/document/d/{}/view'.format(
                        docid)
                mergefields[
                    'downloadcontracturl'] = 'https://docs.google.com/document/d/{}/export?format=pdf'.format(
                        docid)
                # need to bring in full path for email, so use url_root
                # mergefields['_race_'] = sponsordb.race.race
                racedate = SponsorRaceDate.query.filter_by(
                    race_id=sponsordb.race.id,
                    raceyear=sponsordb.raceyear).one()
                mergefields['_racedate_'] = humandt.dt2asc(
                    dt.asc2dt(racedate.racedate))
                mergefields['_coupondate_'] = variables['_coupondate_']
                mergefields['_couponcount_'] = couponcount

                html = template.render(mergefields)
                tolist = sponsordb.client.contactEmail
                rdemail = '{} <{}>'.format(sponsordb.race.racedirector,
                                           sponsordb.race.rdemail)
                cclist = current_app.config['SPONSORSHIPAGREEMENT_CC'] + [
                    rdemail
                ]
                fromlist = '{} <{}>'.format(
                    sponsordb.race.race,
                    current_app.config['SPONSORSHIPQUERY_CONTACT'])
                sendmail(subject, fromlist, tolist, html, ccaddr=cclist)

            # calculate and update trend
            calculateTrend(sponsordb)
            # kludge to force response data to have correct trend
            # TODO: remove when #245 fixed
            thisndx = [i['rowid'] for i in self._responsedata].index(thisid)
            self._responsedata[thisndx]['trend'] = sponsordb.trend