Example #1
0
 def test_notification_flatten(self, render_template):
     '''Test notification kwarg flattener
     '''
     obj = MagicMock()
     obj.__unicode__ = lambda x: 'quux'
     notification = Notification(from_email='*****@*****.**', foo='bar', baz=['qux1', obj])
     self.assertEquals(
         {'foo': 'bar', 'baz': 'qux1; qux2'},
         notification.convert_models(dict(foo='bar', baz=['qux1', 'qux2']))
     )
Example #2
0
    def test_notification_build_multi(self, current_app, render_template):
        '''Test single build messages have multiple recipients
        '''
        current_app.logger = Mock(info=Mock())
        notification = Notification(to_email=['*****@*****.**', '*****@*****.**'], from_email='*****@*****.**')

        # should build two messages on multi send
        msgs = notification._build(multi=False)
        self.assertTrue(len(msgs), 1)
        for msg in msgs:
            self.assertEquals(len(msg.recipients), 2)
Example #3
0
    def test_notification_send_multi(self, send, send_email, render_template):
        '''Test multi builds multiple message objects
        '''
        notification = Notification(to_email=['*****@*****.**', '*****@*****.**'], from_email='*****@*****.**')

        notification.build_msg = Mock()
        notification.build_msg.return_value = []

        # should build two messages on multi send
        notification.send(multi=True)
        self.assertTrue(notification.build_msg.called)
        self.assertEquals(notification.build_msg.call_count, 2)
Example #4
0
    def test_notification_send_single(self, send, send_email, render_template):
        '''Test non-multi only builds one message even with multiple emails
        '''
        notification = Notification(to_email=['*****@*****.**', '*****@*****.**'], from_email='*****@*****.**')

        notification.build_msg = Mock()
        notification.build_msg.return_value = []

        # should build two messages on multi send
        notification.send(multi=False)
        self.assertTrue(notification.build_msg.called)
        self.assertEquals(notification.build_msg.call_count, 1)
Example #5
0
    def test_notification_reshape(self, render_template):
        '''Test notification recipient flattener
        '''
        notification = Notification(to_email='*****@*****.**', from_email='*****@*****.**')
        test_recips = [('a',), ('multi',), ['nested', 'thing']]
        self.assertEquals(
            ['a', 'multi', 'nested', 'thing'],
            notification.flatten(test_recips)
        )

        test_recips_complex = ['a', ['b', ['c', 'd']], ['e']]
        self.assertEquals(
            ['a', 'b', 'c', 'd', 'e'],
            notification.flatten(test_recips_complex)
        )
Example #6
0
    def build_notifications(self):
        '''Implements EmailJobBase build_notifications method

        Returns:
            list of :py:class:`~purchasing.notifications.Notification` objects, one
            for each new Opportunity. For each Opportunity, the ``to_email`` field
            is the union of all followers of the opportunity and any followers of
            any categories that the Opportunity has
        '''
        notifications = []
        for opportunity in self.get_opportunities():
            opp_categories = [i.id for i in opportunity.categories]

            category_vendors = Vendor.query.filter(
                Vendor.categories.any(Category.id.in_(opp_categories))).all()

            notifications.append(
                Notification(
                    to_email=set([i.email for i in category_vendors] +
                                 [i.email for i in opportunity.vendors]),
                    cc_email=list(),
                    from_email=current_app.config['BEACON_SENDER'],
                    subject='A new City of Pittsburgh opportunity from Beacon!',
                    html_template='opportunities/emails/newopp.html',
                    txt_template='opportunities/emails/newopp.txt',
                    opportunity=opportunity))
            opportunity.raw_update(publish_notification_sent=True)

        return notifications
    def send_publish_email(self):
        '''Sends the "new opportunity available" email to subscribed vendors

        If a new Opportunity is created and it has a publish date before or
        on today's date, it will trigger an immediate publish email send. This
        operates in a very similar way to the nightly
        :py:class:`~purchasing.jobs.beacon_nightly.BeaconNewOppotunityOpenJob`.
        It will build a list of all vendors signed up to the Opportunity
        or to any of the categories that describe the Opportunity.
        '''
        if self.is_published and not self.publish_notification_sent:
            opp_categories = [i.id for i in self.categories]

            vendors = Vendor.query.filter(
                Vendor.categories.any(Category.id.in_(opp_categories))).all()

            Notification(
                to_email=[i.email for i in vendors],
                subject='A new City of Pittsburgh opportunity from Beacon!',
                html_template='opportunities/emails/newopp.html',
                txt_template='opportunities/emails/newopp.txt',
                opportunity=self).send(multi=True)

            self.publish_notification_sent = True
            self.published_at = datetime.datetime.utcnow()

            current_app.logger.info(
                u'''BEACON PUBLISHED:  ID: {} | Title: {} | Publish Date: {} | Submission Start Date: {} | Submission End Date: {}
                '''.format(self.id, self.title, str(self.planned_publish),
                           str(self.planned_submission_start),
                           str(self.planned_submission_end)))
            return True
        return False
Example #8
0
    def notify_approvals(self, user):
        '''Send the approval notifications to everyone with approval rights

        Arguments:
            user: A :py:class:`~purchasing.users.models.User` object
        '''
        Notification(
            to_email=[user.email],
            subject='Your post has been sent to OMB for approval',
            html_template='opportunities/emails/staff_postsubmitted.html',
            txt_template='opportunities/emails/staff_postsubmitted.txt',
            opportunity=self).send(multi=True)

        Notification(
            to_email=db.session.query(User.email).join(
                Role, User.role_id == Role.id).filter(
                    Role.name.in_(['conductor', 'admin', 'superadmin'])).all(),
            subject='A new Beacon post needs review',
            html_template='opportunities/emails/admin_postforapproval.html',
            txt_template='opportunities/emails/admin_postforapproval.txt',
            opportunity=self).send(multi=True)
Example #9
0
 def test_notification_initialization(self, render_template):
     '''Test notifications properly initialize
     '''
     notification = Notification(
         from_email='*****@*****.**', to_email='*****@*****.**', cc_email=[('*****@*****.**',), ('*****@*****.**',)]
     )
     self.assertEquals(notification.to_email, ['*****@*****.**'])
     self.assertEquals(notification.from_email, '*****@*****.**')
     self.assertEquals(notification.cc_email, ['*****@*****.**', '*****@*****.**'])
     self.assertEquals(notification.subject, '')
     self.assertEquals(notification.html_body, 'a test')
     self.assertEquals(notification.txt_body, '')
     self.assertEquals(notification.attachments, [])
def feedback_handler(contract, search_for=None):
    '''Allow user to send feedback on the data present in a specific contract

    Arguments:
        contract: :py:class:`~purchasing.data.contracts.ContractBase` object
        search_for: search term or None.

    Returns:
        Redirects to or renders the appropriate feedback handling template
    '''
    form = FeedbackForm()
    search_form = SearchForm()

    if not current_user.is_anonymous():
        form.sender.data = current_user.email

    if form.validate_on_submit():

        current_app.logger.info(
            'WEXFEEDBACK - Feedback from {email} about {contract}'.format(
                email=form.sender.data, contract=contract.description))

        feedback_sent = Notification(
            to_email=db.session.query(User.email).join(
                Role, User.role_id == Role.id).filter(
                    Role.name.in_(['admin', 'superadmin'])).all(),
            subject=
            'Scout contract feedback - ID: {id}, Description: {description}'.
            format(id=contract.id if contract.id else 'N/A',
                   description=contract.description),
            html_template='scout/feedback_email.html',
            contract=contract,
            sender=form.data.get('sender'),
            body=form.data.get('body')).send()

        if feedback_sent:
            flash('Thank you for your feedback!', 'alert-success')
        else:
            flash('Oh no! Something went wrong. We are looking into it.',
                  'alert-danger')

        if contract.id:
            return redirect(url_for('scout.contract', contract_id=contract.id))
        return redirect(url_for('scout.explore'))

    return render_template('scout/feedback.html',
                           search_form=search_form,
                           contract=contract,
                           choices=Department.choices(),
                           feedback_form=form,
                           search_for=search_for)
Example #11
0
 def build_notifications(self):
     '''
     '''
     notifications = []
     for contract in self.get_expiring_contracts():
         notifications.append(
             Notification(
                 to_email=[i.email for i in contract.followers],
                 subject=self.notification_props['subject'],
                 html_template=self.notification_props['html_template'],
                 contract=contract
             )
         )
     return notifications
    def post_validate_action(self, action, contract, current_stage):
        '''Send the email updates

        Arguments:
            action: A
                :py:class:`~purchasing.data.contract_stages.ContractStageActionItem`
                that needs to be updated with details for the action
                log
            contract: A :py:class:`~purchasing.data.contracts.ContractBase` object
            current_stage: The current
                :py:class:`~purchasing.data.contract_stages.ContractStage`

        Returns:
            The modified
            :py:class:`~purchasing.data.contract_stages.ContractStageActionItem`
            with the action detail updated to include the form's data
        '''
        current_app.logger.info(
            'CONDUCTOR EMAIL UPDATE | New update on stage "{}" from contract "{}" (ID: {})'
            .format(current_stage.name, contract.description, contract.id))

        action.action_detail = {
            'sent_to': self.data.get('send_to', ''),
            'body': self.data.get('body'),
            'subject': self.data.get('subject'),
            'stage_name': current_stage.name,
            'attachments': self.get_attachment_filenames()
        }

        Notification(to_email=[
            i.strip() for i in self.data.get('send_to').split(';') if i != ''
        ],
                     from_email=current_app.config['CONDUCTOR_SENDER'],
                     reply_to=current_user.email,
                     cc_email=[
                         i.strip()
                         for i in self.data.get('send_to_cc').split(';')
                         if i != ''
                     ],
                     subject=self.data.get('subject'),
                     html_template='conductor/emails/email_update.html',
                     body=self.data.get('body'),
                     attachments=[
                         i.upload.data for i in self.attachments.entries
                     ]).send(multi=False)

        return action
Example #13
0
def publish(opportunity_id):
    '''Publish an opportunity

    If an :py:class:`~purchasing.opportunities.models.Opportunity` has
    been created by a non-admin, it will be stuck in a "pending" state
    until it has been approved by an admin. This view function handles
    the publication event for a specific
    :py:class:`~purchasing.opportunities.models.Opportunity`

    :status 200: Publish the relevant opportunity and send the relevant
        publication emails
    :status 404: :py:class:`~purchasing.opportunities.models.Opportunity`
        not found
    '''
    opportunity = Opportunity.query.get(opportunity_id)
    if opportunity:
        opportunity.is_public = True
        db.session.commit()
        flash('Opportunity successfully published!', 'alert-success')

        Notification(
            to_email=[opportunity.created_by.email],
            subject='OMB approved your opportunity post!',
            html_template='opportunities/emails/staff_postapproved.html',
            txt_template='opportunities/emails/staff_postapproved.txt',
            opportunity=opportunity).send(multi=True)

        current_app.logger.info(
            '''BEACON APPROVED: ID: {} | Title: {} | Publish Date: {} | Submission Start Date: {} | Submission End Date: {} '''
            .format(opportunity.id,
                    opportunity.title.encode('ascii', 'ignore'),
                    str(opportunity.planned_publish),
                    str(opportunity.planned_submission_start),
                    str(opportunity.planned_submission_end)))

        opportunity.send_publish_email()
        db.session.commit()

        return redirect(url_for('opportunities_admin.pending'))
    abort(404)
Example #14
0
    def build_notifications(self):
        '''Implements EmailJobBase build_notifications method

        Returns:
            list of :py:class:`~purchasing.notifications.Notification` objects, one
            for each non-expired opportunity that has been published since the last
            Beacon newsletter was sent out
        '''
        notifications = []

        opportunities = self.get_opportunities()

        notifications.append(
            Notification(
                to_email=set(
                    [i.email for i in Vendor.newsletter_subscribers()]),
                from_email=current_app.config['BEACON_SENDER'],
                subject='Your biweekly Beacon opportunity summary',
                html_template='opportunities/emails/biweeklydigest.html',
                txt_template='opportunities/emails/biweeklydigest.txt',
                opportunities=opportunities))
        return notifications
def edit_company_contacts(contract_id):
    '''Update information about company contacts, and save all information

    New :py:class:`~purchasing.data.contracts.ContractBase` objects
    are created for each unique controller number. Notifications are
    also sent to all of the original contract's followers to say that
    the contract information has been replaced/updated with new info.

    :param contract_id: Primary key ID for a
        :py:class:`~purchasing.data.contracts.ContractBase`

    .. seealso::

        * :py:class:`~purchasing.conductor.forms.CompanyContactListForm`
        * :py:meth:`~purchasing.data.contracts.ContractBase.create`
        * :py:meth:`~purchasing.data.contracts.ContractBase.complete`
        * :py:class:`~purchasing.notifications.Notification`

    :status 200: Render the CompanyContactListForm form
    :status 302: Post the data and redirect back to the success view, or
        redirect back to contract or company views if those haven't
        been completed yet.
    :status 404: Contract not found
    '''
    contract = ContractBase.query.get(contract_id)

    if contract and session.get(
            'contract-{}'.format(contract_id)) is not None and session.get(
                'companies-{}'.format(contract_id)) is not None:
        form = CompanyContactListForm()

        # pull out companies from session, order them by financial id
        # so that they can be grouped appropriately
        companies = sorted(json.loads(
            session['companies-{}'.format(contract_id)]),
                           key=lambda x: x.get('financial_id'))

        if form.validate_on_submit():
            main_contract = contract
            for ix, _company in enumerate(companies):
                contract_data = json.loads(
                    session['contract-{}'.format(contract_id)])
                # because multiple companies can have the same name, don't use
                # get_or_create because it can create multiples
                if _company.get('company_id') > 0:
                    company = Company.query.get(_company.get('company_id'))
                else:
                    company = Company.create(
                        company_name=_company.get('company_name'))
                # contacts should be unique to companies, though
                try:
                    for _contact in form.data.get('companies')[ix].get(
                            'contacts'):
                        _contact['company_id'] = company.id
                        contact, _ = get_or_create(db.session, CompanyContact,
                                                   **_contact)
                # if there are no contacts, an index error will be thrown for this company
                # so we catch it and just pass
                except IndexError:
                    pass

                contract_data['financial_id'] = _company['financial_id']

                if contract.financial_id is None or contract.financial_id == _company[
                        'financial_id']:
                    contract.update_with_spec_number(contract_data,
                                                     company=company)
                else:
                    contract = ContractBase.clone(contract,
                                                  parent_id=contract.parent_id,
                                                  strip=False)
                    contract.update_with_spec_number(contract_data,
                                                     company=company)

                contract.is_visible = True
                db.session.commit()

            Notification(to_email=[i.email for i in contract.followers],
                         from_email=current_app.config['CONDUCTOR_SENDER'],
                         reply_to=current_user.email,
                         subject='A contract you follow has been updated!',
                         html_template='conductor/emails/new_contract.html',
                         contract=main_contract).send(multi=True)

            session.pop('contract-{}'.format(contract_id))
            session.pop('companies-{}'.format(contract_id))
            session['success-{}'.format(contract_id)] = True

            current_app.logger.info('''
CONDUCTOR CONTRACT COMPLETE - company contacts for contract "{}" assigned. |New contract(s) successfully created'''
                                    .format(contract.description))

            if contract.parent:
                contract.parent.complete()

            return redirect(
                url_for('conductor.success', contract_id=main_contract.id))

        if len(form.companies.entries) == 0:
            for company in companies:
                form.companies.append_entry()

        return render_template('conductor/edit/edit_company_contacts.html',
                               form=form,
                               contract=contract,
                               companies=companies)
    elif session.get('contract-{}'.format(contract_id)) is None:
        return redirect(url_for('conductor.edit', contract_id=contract_id))
    elif session.get('companies-{}'.format(contract_id)) is None:
        return redirect(
            url_for('conductor.edit_company', contract_id=contract_id))
    abort(404)
Example #16
0
def signup_for_opp(form, opportunity, multi=False):
    '''Sign a vendor up for an opportunity

    Generic helper method to handle subscriptions from both the list view
    (signing up form multiple opportunities) and the detail view (signing
    up for a single opportunity). Responsible for creation of new Vendor
    objects if necessary, and sending emails based on the opportunities
    selected to receive updates about.

    Arguments:
        form: The relevant subscription form
        opportunity: Either an opportunity model or a list of opportunity ids
        multi: A boolean to flag if there are multiple opportunities that should
            to subscribe to or a single opportunity

    Returns:
        True if email sent successfully, false otherwise
    '''
    send_email = True
    email_opportunities = []
    if opportunity is None or (isinstance(opportunity, list)
                               and len(opportunity) == 0):
        form.errors['opportunities'] = [
            'You must select at least one opportunity!'
        ]
        return False
    # add the email/business name to the session
    session['email'] = form.data.get('email')
    session['business_name'] = form.data.get('business_name')
    # subscribe the vendor to the opportunity
    vendor = Vendor.query.filter(
        Vendor.email == form.data.get('email')).first()

    if vendor is None:
        vendor = Vendor(email=form.data.get('email'),
                        business_name=form.data.get('business_name'))
        db.session.add(vendor)
        db.session.commit()
    else:
        vendor.update(business_name=form.data.get('business_name'))

    if multi:
        for opp in opportunity:
            _opp = Opportunity.query.get(int(opp))
            if not _opp.is_public:
                db.session.rollback()
                form.errors['opportunities'] = ['That\'s not a valid choice.']
                return False
            if _opp in vendor.opportunities:
                send_email = False
            else:
                vendor.opportunities.add(_opp)
                email_opportunities.append(_opp)
    else:
        if opportunity in vendor.opportunities:
            send_email = False
        else:
            vendor.opportunities.add(opportunity)
            email_opportunities.append(opportunity)

    if form.data.get('also_categories'):
        # TODO -- add support for categories
        pass

    db.session.commit()

    current_app.logger.info(
        u'OPPSIGNUP - Vendor has signed up for opportunities: EMAIL: {email} at BUSINESS: {bis_name} signed up for:\n'
        + u'OPPORTUNITY: {opportunities}'.format(
            email=form.data.get('email'),
            business_name=form.data.get('business_name'),
            opportunities=', '.join([i.title for i in email_opportunities])))

    if send_email:
        Notification(to_email=vendor.email,
                     from_email=current_app.config['BEACON_SENDER'],
                     subject='Subscription confirmation from Beacon',
                     html_template='opportunities/emails/oppselected.html',
                     txt_template='opportunities/emails/oppselected.txt',
                     opportunities=email_opportunities).send()

    return True