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"])) )
def test_notification_reshape(self, render_template): """Test notification recipient flattener """ notification = Notification(to_email="*****@*****.**", from_email="*****@*****.**") test_recips = [("a",), ("multi",), ["nested", "thing"]] self.assertListEqual(["a", "multi", "nested", "thing"], notification.flatten(test_recips)) test_recips_complex = ["a", ["b", ["c", "d"]], ["e"]] self.assertListEqual(["a", "b", "c", "d", "e"], notification.flatten(test_recips_complex))
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'])) )
def test_notification_build_multi(self, current_app, render_template): '''Test multi-messages only have one recipient ''' current_app.logger = Mock(info=Mock()) notification = Notification(to_email=['*****@*****.**', '*****@*****.**'], from_email='*****@*****.**') # should build two messages on multi send msgs = notification._build(multi=True) self.assertTrue(len(msgs), 2) for msg in msgs: self.assertEquals(len(msg.recipients), 1)
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)
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)
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)
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)
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)
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)
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) )
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: vendors = Vendor.query.filter( Vendor.categories.any(Category.id.in_( self.get_category_ids()))).all() Notification( to_email=[i.email for i in vendors], subject='A new City of Pittsburgh opportunity from Beacon!', html_template='beacon/emails/newopp.html', txt_template='beacon/emails/newopp.txt', opportunity=self).send(multi=True) self.publish_notification_sent = True self.published_at = datetime.datetime.utcnow() current_app.logger.info( '''BEACON PUBLISHED: ID: {} | Title: {} | Publish Date: {} | Submission Start Date: {} | Submission End Date: {} '''.format(self.id, self.title.encode('ascii', 'ignore'), str(self.planned_publish), str(self.planned_submission_start), str(self.planned_submission_end))) return True return False
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(): category_vendors = Vendor.query.filter( Vendor.categories.any(Category.id.in_(opportunity.get_category_ids())) ).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='beacon/emails/newopp.html', txt_template='beacon/emails/newopp.txt', opportunity=opportunity ) ) opportunity.raw_update(publish_notification_sent=True) return notifications
def notify_approvals(self, user): '''Send the approval notifications to everyone with approval rights Arguments: user: A :py:class:`~purchasing.models.users.User` object ''' Notification(to_email=[user.email], subject='Your post has been sent to OMB for approval', html_template='beacon/emails/staff_postsubmitted.html', txt_template='beacon/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='beacon/emails/admin_postforapproval.html', txt_template='beacon/emails/admin_postforapproval.txt', opportunity=self).send(multi=True)
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 publish(opportunity_id): '''Publish an opportunity If an :py:class:`~purchasing.models.front.Opportunity` has been created by a non-admin, it will be stuck in a "pending" state until it has been approved by an beacon_admin. This view function handles the publication event for a specific :py:class:`~purchasing.models.front.Opportunity` :status 200: Publish the relevant opportunity and send the relevant publication emails :status 404: :py:class:`~purchasing.models.front.Opportunity` not found ''' opportunity = db.session.query(Opportunity).filter( Opportunity.id == opportunity_id).first() if opportunity: flash('Opportunity successfully published!', 'alert-success') opportunity.is_public = True db.session.flush() opportunity.send_publish_email() db.session.commit() Notification(to_email=[opportunity.created_by.email], subject='OMB approved your opportunity post!', html_template='beacon/emails/staff_postapproved.html', txt_template='beacon/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))) return redirect(url_for('beacon_admin.pending')) abort(404)
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='beacon/emails/biweeklydigest.html', txt_template='beacon/emails/biweeklydigest.txt', opportunities=opportunities ) ) return notifications
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( 'OPPSIGNUP - Vendor has signed up for opportunities: EMAIL: {email} at BUSINESS: {bis_name} signed up for:\n' + 'OPPORTUNITY: {opportunities}'.format( email=form.data.get('email'), business_name=form.data.get('business_name'), opportunities=', '.join([ i.title.encode('ascii', 'ignore') 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='beacon/emails/oppselected.html', txt_template='beacon/emails/oppselected.txt', opportunities=email_opportunities).send() return True
def signup(): '''The signup page for vendors :status 200: Render the :py:class:`~purchasing.forms.front.SignupForm` :status 302: Process the signup form post, redirect the vendor back to the splash page ''' session_vendor = Vendor.query.filter( Vendor.email == session.get('email'), Vendor.business_name == session.get('business_name') ).first() form = init_form(VendorSignupForm, model=session_vendor) if form.validate_on_submit(): vendor = Vendor.query.filter(Vendor.email == form.data.get('email')).first() if vendor: current_app.logger.info(''' OPPUPDATEVENDOR - Vendor updated: EMAIL: {old_email} -> {email} at BUSINESS: {old_bis} -> {bis_name} signed up for: CATEGORIES: {old_cats} -> {categories}'''.format( old_email=vendor.email, email=form.data['email'], old_bis=vendor.business_name, bis_name=form.data['business_name'], old_cats=[i.__unicode__() for i in vendor.categories], categories=[i.__unicode__() for i in form.data['categories']] )) vendor.update( **form.pop_categories(categories=False) ) flash("You are already signed up! Your profile was updated with this new information", 'alert-info') else: current_app.logger.info( 'OPPNEWVENDOR - New vendor signed up: EMAIL: {email} at BUSINESS: {bis_name} signed up for:\n' + 'CATEGORIES: {categories}'.format( email=form.data['email'], bis_name=form.data['business_name'], categories=[i.__unicode__() for i in form.data['categories']] ) ) vendor = Vendor.create( **form.pop_categories(categories=False) ) confirmation_sent = Notification( to_email=vendor.email, subject='Thank you for signing up!', html_template='beacon/emails/signup.html', txt_template='beacon/emails/signup.txt', categories=form.data['categories'] ).send() if confirmation_sent: admins = db.session.query(User.email).join(Role, User.role_id == Role.id).filter( Role.name.in_(['admin', 'superadmin']) ).all() Notification( to_email=admins, subject='A new vendor has signed up on beacon', categories=form.data['categories'], vendor=form.data['email'], convert_args=True, business_name=form.data['business_name'] ).send() flash('Thank you for signing up! Check your email for more information', 'alert-success') else: flash('Uh oh, something went wrong. We are investigating.', 'alert-danger') session['email'] = form.data.get('email') session['business_name'] = form.data.get('business_name') return redirect(url_for('front.splash')) page_email = request.args.get('email', None) if page_email: current_app.logger.info( 'OPPSIGNUPVIEW - User clicked through to signup with email {}'.format(page_email) ) session['email'] = page_email return redirect(url_for('front.signup')) if 'email' in session: if not form.email.validate(form): session.pop('email', None) form.display_cleanup() return render_template( 'beacon/front/signup.html', form=form, categories=form.get_categories(), subcategories=form.get_subcategories() )