def users(): form = NewUserForm() if form.validate_on_submit(): email, name = form.email.data, form.name.data user = User(email, name) db.session.add(user) db.session.commit() app.logger.info( "%s manually created new user with email %s and id: %s", current_user.id, email, user.id, ) code = user.login_code(app.config["SECRET_KEY"]) msg = EmailMessage( "Welcome to the EMF website", from_email=from_email("CONTACT_EMAIL"), to=[email], ) msg.body = render_template("emails/manually-added-user.txt", user=user, code=code) msg.send() flash("Created account for: %s" % name) return redirect(url_for(".users")) users = User.query.order_by(User.id).options(joinedload( User.permissions)).all() return render_template("admin/users/users.html", users=users, form=form)
def stripe_payment_paid(payment: StripePayment): if payment.state == "paid": logger.info("Payment is already paid, ignoring") return if payment.state == "partrefunded": logger.info("Payment is already partially refunded, ignoring") return logger.info("Setting payment %s to paid", payment.id) payment.paid() db.session.commit() msg = EmailMessage( "Your EMF payment has been confirmed", from_email=from_email("TICKETS_EMAIL"), to=[payment.user.email], ) already_emailed = set_tickets_emailed(payment.user) msg.body = render_template( "emails/tickets-paid-email-stripe.txt", user=payment.user, payment=payment, already_emailed=already_emailed, ) if feature_enabled("ISSUE_TICKETS"): attach_tickets(msg, payment.user) msg.send() db.session.commit()
def email_tickets(): """Email tickets to those who haven't received them""" users_purchase_counts = (Purchase.query.filter_by( is_paid_for=True, state="paid").join(PriceTier, Product, ProductGroup).filter( ProductGroup.type.in_(RECEIPT_TYPES)).join( Purchase.owner).with_entities(User, func.count( Purchase.id)).group_by(User).order_by(User.id)) for user, purchase_count in users_purchase_counts: plural = purchase_count != 1 and "s" or "" msg = EmailMessage( "Your Electromagnetic Field Ticket%s" % plural, from_email=from_email("TICKETS_EMAIL"), to=[user.email], ) already_emailed = set_tickets_emailed(user) msg.body = render_template("emails/receipt.txt", user=user, already_emailed=already_emailed) attach_tickets(msg, user) app.logger.info("Emailing %s receipt for %s tickets", user.email, purchase_count) msg.send() db.session.commit()
def transaction_reconcile(txn_id, payment_id): txn = BankTransaction.query.get_or_404(txn_id) payment = BankPayment.query.get_or_404(payment_id) form = ManualReconcilePaymentForm() if form.validate_on_submit(): if form.reconcile.data: app.logger.info( "%s manually reconciling against payment %s (%s) by %s", current_user.name, payment.id, payment.bankref, payment.user.email, ) if txn.payment: app.logger.error("Transaction already reconciled") flash("Transaction %s already reconciled" % txn.id) return redirect(url_for("admin.transactions")) payment.lock() if payment.state == "paid": app.logger.error("Payment has already been paid") flash("Payment %s already paid" % payment.id) return redirect(url_for("admin.transactions")) txn.payment = payment payment.paid() db.session.commit() msg = EmailMessage( "Electromagnetic Field ticket purchase update", from_email=from_email("TICKETS_EMAIL"), to=[payment.user.email], ) already_emailed = set_tickets_emailed(payment.user) msg.body = render_template( "emails/tickets-paid-email-banktransfer.txt", user=payment.user, payment=payment, already_emailed=already_emailed, ) if feature_enabled("ISSUE_TICKETS"): attach_tickets(msg, payment.user) msg.send() flash("Payment ID %s marked as paid" % payment.id) return redirect(url_for("admin.transactions")) return render_template("admin/accounts/txn-reconcile.html", txn=txn, payment=payment, form=form)
def test_send_without_sender(self): self.app.extensions['mailman'].default_sender = None msg = EmailMessage(subject="testing", to=["*****@*****.**"], body="testing") msg.send() self.assertEqual(len(self.mail.outbox), 1) sent_msg = self.mail.outbox[0] self.assertEqual(sent_msg.from_email, None)
def handle_free_tickets(flow: str, view: ProductView, basket: Basket): """The user is trying to "buy" only free tickets. This is effectively a payment stage, handled differently from the rest of the flow. """ # They must be authenticated for this. if not current_user.is_authenticated: app.logger.warn("User is not authenticated, sending to login") flash("You must be logged in to buy additional free tickets") return redirect( url_for("users.login", next=url_for("tickets.main", flow=flow))) # We sell under-12 tickets to non-CfP users, to enforce capacity. # We don't let people order an under-12 ticket on its own. # However, CfP users need to be able to buy day and parking tickets. admissions_tickets = current_user.get_owned_tickets( type="admission_ticket") if not any(admissions_tickets) and not view.cfp_accepted_only: app.logger.warn( "User trying to buy free add-ons without an admission ticket") flash( "You must have an admissions ticket to buy additional free tickets" ) return redirect(url_for("tickets.main", flow=flow)) basket.user = current_user basket.check_out_free() db.session.commit() Basket.clear_from_session() msg = EmailMessage( "Your EMF ticket order", from_email=from_email("TICKETS_EMAIL"), to=[current_user.email], ) already_emailed = set_tickets_emailed(current_user) msg.body = render_template( "emails/tickets-ordered-email-free.txt", user=current_user, basket=basket, already_emailed=already_emailed, ) if feature_enabled("ISSUE_TICKETS"): attach_tickets(msg, current_user) msg.send() if len(basket.purchases) == 1: flash("Your ticket has been confirmed") else: flash("Your tickets have been confirmed") return redirect(url_for("users.purchases"))
def test_invalid_backend(self): self.app.extensions['mailman'].backend = 'unknown' msg = EmailMessage( subject="testing", to=["*****@*****.**"], body="testing", ) with pytest.raises(RuntimeError) as exc: msg.send() assert "The available built-in mail backends" in str(exc)
def test_send_message(self): msg = EmailMessage( subject="testing", to=["*****@*****.**"], body="testing", ) msg.send() self.assertEqual(len(self.mail.outbox), 1) sent_msg = self.mail.outbox[0] self.assertEqual(sent_msg.from_email, self.app.extensions["mailman"].default_sender)
def test_console_backend(self): self.app.extensions['mailman'].backend = 'console' msg = EmailMessage( subject="testing", to=["*****@*****.**"], body="testing", ) msg.send() captured = self.capsys.readouterr() assert "testing" in captured.out assert "To: [email protected]" in captured.out
def test_override_custom_backend(self): self.app.extensions['mailman'].backend = 'console' with self.mail.get_connection(backend=locmem.EmailBackend) as conn: msg = EmailMessage(subject="testing", to=["*****@*****.**"], body="testing", connection=conn) msg.send() self.assertEqual(len(self.mail.outbox), 1) sent_msg = self.mail.outbox[0] self.assertEqual(sent_msg.subject, "testing")
def ticket_admin_email(title, template, **kwargs): if not app.config.get("TICKETS_NOTICE_EMAIL"): app.logger.warning("No tickets notice email configured, not sending") return msg = EmailMessage( title, from_email=from_email("TICKETS_EMAIL"), to=[app.config["TICKETS_NOTICE_EMAIL"][1]], ) msg.body = render_template(template, **kwargs) msg.send()
def signup(): if not request.args.get("code"): abort(404) uid = verify_signup_code(app.config["SECRET_KEY"], time.time(), request.args.get("code")) if uid is None: flash( "Your signup link was invalid. Please note that they expire after 6 hours." ) abort(404) user = User.query.get_or_404(uid) if not user.has_permission("admin"): app.logger.warn("Signup link resolves to non-admin user %s", user) abort(404) form = SignupForm() if current_user.is_authenticated: return redirect(url_for(".account")) if form.validate_on_submit(): email, name = form.email.data, form.name.data user = User(email, name) if form.allow_promo.data: user.promo_opt_in = True db.session.add(user) db.session.commit() app.logger.info("Signed up new user with email %s and id %s", email, user.id) msg = EmailMessage( "Welcome to the EMF website", from_email=from_email("CONTACT_EMAIL"), to=[email], ) msg.body = render_template("emails/signup-user.txt", user=user) msg.send() login_user(user) return redirect(url_for(".account")) if request.args.get("email"): form.email.data = request.args.get("email") return render_template("account/signup.html", form=form)
def test_filebased_backend(self): with tempfile.TemporaryDirectory() as tempdir: self.app.extensions['mailman'].backend = 'file' self.app.extensions['mailman'].file_path = tempdir with self.mail.get_connection() as conn: msg = EmailMessage( subject="testing", to=["*****@*****.**"], body="testing", connection=conn, ) msg.send() wrote_file = Path(conn._fname) assert wrote_file.is_file() assert "To: [email protected]" in wrote_file.read_text()
def send_email(subject, body, send_to): try: msg = EmailMessage(subject, body, Config.MAIL_USERNAME, [send_to]) msg.content_subtype = "html" msg.send() return True except: current_app.logger.info( 'ERR - Cannot send email, please check MAIL settings') current_app.logger.info(' -> MAIL Subject: ' + subject) current_app.logger.info(' -> MAIL Body: ' + body) return False
def send_refund_email(request: RefundRequest, amount: Decimal) -> None: payment = request.payment msg = EmailMessage( "Your refund request has been processed", from_email=from_email("TICKETS_EMAIL"), to=[payment.user.email], ) # TODO: handle situation where currency of refund is different from currency of payment. msg.body = render_template( "emails/refund-sent.txt", user=payment.user, amount=amount, request=request, currency=payment.currency, ) msg.send()
def test_locmem_backend(self): self.app.extensions['mailman'].backend = 'locmem' msg = EmailMessage( subject="testing", to=["*****@*****.**"], body="testing", ) msg.send() self.assertEqual(len(self.mail.outbox), 1) sent_msg = self.mail.outbox[0] self.assertEqual(sent_msg.subject, "testing") self.assertEqual(sent_msg.to, ["*****@*****.**"]) self.assertEqual(sent_msg.body, "testing") self.assertEqual(sent_msg.from_email, self.app.extensions["mailman"].default_sender)
def test_send_single(self): with self.mail.get_connection() as conn: msg = EmailMessage( subject="testing", to=["*****@*****.**"], body="testing", connection=conn, ) msg.send() self.assertEqual(len(self.mail.outbox), 1) sent_msg = self.mail.outbox[0] self.assertEqual(sent_msg.subject, "testing") self.assertEqual(sent_msg.to, ["*****@*****.**"]) self.assertEqual(sent_msg.body, "testing") self.assertEqual(sent_msg.from_email, self.app.extensions["mailman"].default_sender)
def send_reminder(payment_id): payment = BankPayment.query.get_or_404(payment_id) form = SendReminderForm() if form.validate_on_submit(): if form.remind.data: app.logger.info( "%s sending reminder email to %s <%s> for payment %s", current_user.name, payment.user.name, payment.user.email, payment.id, ) payment.lock() if payment.reminder_sent_at: app.logger.error("Reminder for payment %s already sent", payment.id) flash("Cannot send duplicate reminder email for payment %s" % payment.id) return redirect(url_for("admin.expiring")) msg = EmailMessage( "Electromagnetic Field: Your tickets will expire in five days", from_email=from_email("TICKETS_EMAIL"), to=[payment.user.email], ) msg.body = render_template( "emails/tickets-reminder.txt", payment=payment, account=payment.recommended_destination, ) msg.send() payment.reminder_sent_at = datetime.utcnow() db.session.commit() flash("Reminder email for payment %s sent" % payment.id) return redirect(url_for("admin.expiring")) return render_template( "admin/payments/payment-send-reminder.html", payment=payment, account=payment.recommended_destination, form=form, )
def test_dummy_backend(self): self.app.extensions['mailman'].backend = 'dummy' msg = EmailMessage( subject="testing", to=["*****@*****.**"], body="testing", ) assert msg.send() == 1
def login(): if current_user.is_authenticated: return redirect(get_next_url()) if request.args.get("code"): user = User.get_by_code(app.config["SECRET_KEY"], request.args.get("code")) if user is not None: login_user(user) session.permanent = True return redirect(get_next_url()) else: flash( "Your login link was invalid. Please enter your email address below to receive a new link." ) form = LoginForm(request.form) if form.validate_on_submit(): code = form._user.login_code(app.config["SECRET_KEY"]) msg = EmailMessage( "Electromagnetic Field: Login details", from_email=from_email("TICKETS_EMAIL"), to=[form._user.email], ) msg.body = render_template( "emails/login-code.txt", user=form._user, code=code, next_url=get_next_url(), ) msg.send() flash("We've sent you an email with your login link") if request.args.get("email"): form.email.data = request.args.get("email") return render_template( "account/login.html", form=form, next=get_next_url(), )
def send_confirmation(payment: BankPayment): msg = EmailMessage( "Electromagnetic Field ticket purchase update", from_email=from_email("TICKETS_EMAIL"), to=[payment.user.email], ) already_emailed = set_tickets_emailed(payment.user) msg.body = render_template( "emails/tickets-paid-email-banktransfer.txt", user=payment.user, payment=payment, already_emailed=already_emailed, ) if feature_enabled("ISSUE_TICKETS"): attach_tickets(msg, payment.user) msg.send() db.session.commit()
def test_smtp_backend(self): self.app.extensions['mailman'].backend = 'smtp' msg = EmailMessage( subject="testing", to=["*****@*****.**"], body="testing", ) with patch.object(smtp.EmailBackend, 'send_messages') as mock_send_fn: mock_send_fn.return_value = 66 assert msg.send() == 66
def background_task( channel_url, text, subscribers, subject, mfrom, send_uid, attachments=[] ): logger.info( 'Start send "{subject}" to {subscribers} recipients through channel "{channel}".'.format( # noqa subject=subject, subscribers=len(subscribers), channel=channel_url ) ) try: with app.mail.get_connection() as conn: for i, mto in enumerate(subscribers): msg = EmailMessage( from_email=mfrom, to=[mto], body=text, subject=subject, connection=conn, ) msg.content_subtype = "html" for attachment in attachments: msg.attach( filename=attachment.get("filename", ""), content=attachment.get("data", ""), mimetype=attachment.get("content_type", ""), ) try: msg.send() except SMTPRecipientsRefused: logger.info("[SKIP] - {}: invalid address.".format(mto)) if (i + 1) % 1000 == 0: logger.info( "- Sending status: {}/{}".format(i + 1, len(subscribers)) ) except Exception as e: logger.error("Message not sent:") logger.exception(e) send_complete(channel_url=channel_url, send_uid=send_uid, error=True) return send_complete(channel_url=channel_url, send_uid=send_uid) logger.info("Task complete.")
def transfer_start(payment: BankPayment): if not feature_enabled("BANK_TRANSFER"): return redirect(url_for("tickets.pay")) if get_user_currency() == "EUR" and not feature_enabled("BANK_TRANSFER_EURO"): return redirect(url_for("tickets.pay")) logger.info("Created bank payment %s (%s)", payment.id, payment.bankref) # No extra preparation required for bank transfer. We can go straight to inprogress. if payment.currency == "GBP": days = app.config.get("EXPIRY_DAYS_TRANSFER") elif payment.currency == "EUR": days = app.config.get("EXPIRY_DAYS_TRANSFER_EURO") if days is None: raise Exception("EXPIRY_DAYS_TRANSFER(_EURO) not set") payment.expires = datetime.utcnow() + timedelta(days=days) payment.state = "inprogress" for purchase in payment.purchases: purchase.set_state("payment-pending") db.session.commit() msg = EmailMessage( "Your EMF ticket purchase", from_email=from_email("TICKETS_EMAIL"), to=[current_user.email], ) msg.body = render_template( "emails/tickets-purchased-email-banktransfer.txt", user=current_user, payment=payment, ) msg.send() return redirect(url_for("payments.transfer_waiting", payment_id=payment.id))
def send_email_for_proposal(proposal, reason="still-considered", from_address=None): if reason == "accepted": subject = 'Your EMF proposal "%s" has been accepted!' % proposal.title template = "cfp_review/email/accepted_msg.txt" elif reason == "still-considered": subject = 'We\'re still considering your EMF proposal "%s"' % proposal.title template = "cfp_review/email/not_accepted_msg.txt" elif reason == "rejected": proposal.has_rejected_email = True subject = 'Your EMF proposal "%s" was not accepted.' % proposal.title template = "emails/cfp-rejected.txt" elif reason == "check-your-slot": subject = ( "Your EMF proposal '%s' has been scheduled, please check your slot" % proposal.title ) template = "emails/cfp-check-your-slot.txt" elif reason == "please-finalise": subject = "We need information about your EMF proposal '%s'" % proposal.title template = "emails/cfp-please-finalise.txt" elif reason == "reserve-list": subject = "Your EMF proposal '%s', and EMF tickets" % proposal.title template = "emails/cfp-reserve-list.txt" elif reason == "scheduled": subject = "Your EMF %s has been scheduled ('%s')" % ( proposal.human_type, proposal.title, ) template = "emails/cfp-slot-scheduled.txt" elif reason == "moved": subject = "Your EMF %s slot has been moved ('%s')" % ( proposal.human_type, proposal.title, ) template = "emails/cfp-slot-moved.txt" else: raise Exception("Unknown cfp proposal email type %s" % reason) app.logger.info("Sending %s email for proposal %s", reason, proposal.id) send_from = from_email("CONTENT_EMAIL") if from_address: send_from = from_address msg = EmailMessage(subject, from_email=send_from, to=[proposal.user.email]) msg.body = render_template( template, user=proposal.user, proposal=proposal, reserve_ticket_link=app.config["RESERVE_LIST_TICKET_LINK"], ) msg.send()
def tickets_issue_free(email): user = User.get_by_email(email) if user is None: form = IssueFreeTicketsNewUserForm() new_user = True else: form = IssueTicketsForm() new_user = False free_pts = (PriceTier.query.join(Product).filter(~PriceTier.prices.any( Price.price_int > 0)).order_by(Product.name).all()) form.add_price_tiers(free_pts) if form.validate_on_submit(): if not user: app.logger.info("Creating new user with email %s and name %s", email, form.name.data) user = User(email, form.name.data) db.session.add(user) flash("Created account for %s" % email) basket = form.create_basket(user) app.logger.info("Admin basket for %s %s", user.email, basket) try: basket.create_purchases() basket.ensure_purchase_capacity() assert basket.total == 0 except CapacityException as e: db.session.rollback() app.logger.warn("Limit exceeded creating admin tickets: %s", e) return redirect(url_for(".tickets_issue_free", email=email)) for p in basket.purchases: p.set_state("paid") app.logger.info("Allocated %s tickets to user", len(basket.purchases)) db.session.commit() code = user.login_code(app.config["SECRET_KEY"]) ticket_noun = "tickets" if len(basket.purchases) > 1 else "ticket" msg = EmailMessage( f"Your complimentary {ticket_noun} to Electromagnetic Field", from_email=from_email("TICKETS_EMAIL"), to=[user.email], ) already_emailed = set_tickets_emailed(user) msg.body = render_template( "emails/tickets-free.txt", user=user, code=code, tickets=basket.purchases, new_user=new_user, already_emailed=already_emailed, ) if feature_enabled("ISSUE_TICKETS"): attach_tickets(msg, user) msg.send() db.session.commit() flash(f"Allocated {len(basket.purchases)} {ticket_noun}") return redirect(url_for(".tickets_issue")) return render_template("admin/tickets/tickets-issue-free.html", form=form, user=user, email=email)
def test_bad_header_subject(self): msg = EmailMessage(subject="testing\n\r", body="testing", to=["*****@*****.**"]) with pytest.raises(BadHeaderError): msg.send()
def test_send_without_to(self): msg = EmailMessage(subject="testing", to=[], body="testing") assert msg.send() == 0
def tickets_reserve(email): user = User.get_by_email(email) if user is None: form = ReserveTicketsNewUserForm() new_user = True else: form = ReserveTicketsForm() new_user = False pts = (PriceTier.query.join(Product, ProductGroup).order_by(ProductGroup.name, Product.display_name, Product.id).all()) form.add_price_tiers(pts) if form.validate_on_submit(): if not user: name = form.name.data app.logger.info("Creating new user with email %s and name %s", email, name) user = User(email, name) flash("Created account for %s" % name) db.session.add(user) basket = form.create_basket(user) app.logger.info("Admin basket for %s %s", user.email, basket) try: basket.create_purchases() basket.ensure_purchase_capacity() db.session.commit() except CapacityException as e: db.session.rollback() app.logger.warn("Limit exceeded creating admin tickets: %s", e) return redirect(url_for(".tickets_reserve", email=email)) code = user.login_code(app.config["SECRET_KEY"]) ticket_noun = "tickets" if len(basket.purchases) > 1 else "ticket" msg = EmailMessage( f"Your reserved {ticket_noun} to EMF", from_email=from_email("TICKETS_EMAIL"), to=[user.email], ) msg.body = render_template( "emails/tickets-reserved.txt", user=user, code=code, tickets=basket.purchases, new_user=new_user, currency=form.currency.data, ) msg.send() db.session.commit() flash(f"Reserved {ticket_noun} and emailed {user.email}") return redirect(url_for(".tickets_issue")) return render_template("admin/tickets/tickets-reserve.html", form=form, pts=pts, user=user)
def transfer(ticket_id): try: ticket = current_user.owned_purchases.filter_by(id=ticket_id).one() except NoResultFound: abort(404) if not ticket.is_paid_for: flash("Unpaid tickets cannot be transferred") return redirect(url_for("users.purchases")) if not ticket.product.get_attribute("is_transferable"): flash("This purchase cannot be transferred") return redirect(url_for("users.purchases")) form = TicketTransferForm() if form.validate_on_submit(): email = form.email.data if not User.does_user_exist(email): new_user = True # Create a new user to transfer the ticket to to_user = User(email, form.name.data) db.session.add(to_user) db.session.commit() else: new_user = False to_user = User.get_by_email(email) ticket = Ticket.query.with_for_update().get(ticket_id) assert ticket.owner_id == current_user.id ticket.transfer(from_user=current_user, to_user=to_user) db.session.commit() app.logger.info("Ticket %s transferred from %s to %s", ticket, current_user, to_user) # Alert the users via email code = to_user.login_code(app.config["SECRET_KEY"]) msg = EmailMessage( "You've been sent a ticket to EMF!", from_email=from_email("TICKETS_EMAIL"), to=[to_user.email], ) already_emailed = set_tickets_emailed(to_user) msg.body = render_template( "emails/ticket-transfer-new-owner.txt", to_user=to_user, from_user=current_user, new_user=new_user, code=code, already_emailed=already_emailed, ) if feature_enabled("ISSUE_TICKETS"): attach_tickets(msg, to_user) msg.send() db.session.commit() msg = EmailMessage( "You sent someone an EMF ticket", from_email=from_email("TICKETS_EMAIL"), to=[current_user.email], ) msg.body = render_template( "emails/ticket-transfer-original-owner.txt", to_user=to_user, from_user=current_user, ) msg.send() flash("Your ticket was transferred.") return redirect(url_for("users.purchases")) return render_template("tickets/transfer.html", ticket=ticket, form=form)