def _send_mail(order: Order, subject: LazyI18nString, message: LazyI18nString, subevent: SubEvent, refund_amount: Decimal, user: User, positions: list): with language(order.locale, order.event.settings.region): try: ia = order.invoice_address except InvoiceAddress.DoesNotExist: ia = InvoiceAddress(order=order) email_context = get_email_context(event_or_subevent=subevent or order.event, refund_amount=refund_amount, order=order, position_or_address=ia, event=order.event) real_subject = str(subject).format_map(TolerantDict(email_context)) try: order.send_mail( real_subject, message, email_context, 'pretix.event.order.email.event_canceled', user, ) except SendMailException: logger.exception('Order canceled email could not be sent') for p in positions: if subevent and p.subevent_id != subevent.id: continue if p.addon_to_id is None and p.attendee_email and p.attendee_email != order.email: real_subject = str(subject).format_map( TolerantDict(email_context)) email_context = get_email_context(event_or_subevent=p.subevent or order.event, event=order.event, refund_amount=refund_amount, position_or_address=p, order=order, position=p) try: order.send_mail(real_subject, message, email_context, 'pretix.event.order.email.event_canceled', position=p, user=user) except SendMailException: logger.exception( 'Order canceled email could not be sent to attendee')
def post(self, request, *args, **kwargs): if not self.link_form.is_valid(): messages.error(self.request, _('We had difficulties processing your input.')) return self.get(request, *args, **kwargs) user = self.link_form.cleaned_data.get('email') if settings.HAS_REDIS: from django_redis import get_redis_connection rc = get_redis_connection("redis") if rc.exists('pretix_resend_{}'.format(user)): messages.error(request, _('We already sent you an email in the last 24 hours.')) return redirect(eventreverse(self.request.event, 'presale:event.resend_link')) else: rc.setex('pretix_resend_{}'.format(user), 3600 * 24, '1') orders = self.request.event.orders.filter(email__iexact=user) if not orders: user = INVALID_ADDRESS subject = _('Your orders for {}').format(self.request.event) template = self.request.event.settings.mail_text_resend_all_links context = get_email_context(event=self.request.event, orders=orders) try: mail(user, subject, template, context, event=self.request.event, locale=self.request.LANGUAGE_CODE) except SendMailException: logger = logging.getLogger('pretix.presale.user') logger.exception('A mail resending order links to {} could not be sent.'.format(user)) messages.error(self.request, _('We have trouble sending emails right now, please check back later.')) return self.get(request, *args, **kwargs) messages.success(self.request, _('If there were any orders by this user, they will receive an email with their order codes.')) return redirect(eventreverse(self.request.event, 'presale:event.index'))
def twilio_order_message(order, template_name): from .tasks import twilio_send recipient = order.phone if not order.phone: return context = get_email_context(event=order.event, order=order) for k, v in order.event.meta_data.items(): context["meta_" + k] = v with language(order.locale, order.event.settings.region): template = order.event.settings.get(f"twilio_text_{template_name}") if not str(template): return try: content = render_mail(template, context) twilio_send(text=content, to=str(recipient), event=order.event_id) except Exception: raise else: order.log_action( "pretix_twilio.message.sent", data={ "message": content, "recipient": str(recipient), }, )
def send_payment_reminders(sender, **kwargs): with scopes_disabled(): dd = SepaDueDate.objects.filter( reminded=False, remind_after__lt=now(), payment__state=OrderPayment.PAYMENT_STATE_CONFIRMED ).select_related( 'payment', 'payment__order').prefetch_related('payment__order__event') for due_date in dd: order = due_date.payment.order event = order.event subject = event.settings.payment_sepadebit_pre_notification_mail_subject text = event.settings.payment_sepadebit_pre_notification_mail_body ctx = get_email_context(event=event, order=order, sepa_debit_payment=due_date.payment) with language(order.locale, event.settings.region): due_date.payment.order.send_mail( subject=str(subject), template=text, context=ctx, log_entry_type= 'pretix_sepadebit.payment_reminder.sent.order.email') due_date.reminded = True due_date.save()
def send_email(self, event, position): op = OrderPosition.objects.get(pk=position) with language(op.order.locale, event.settings.region): email_template = event.settings.cwa_checkin_email_body email_subject = str(event.settings.cwa_checkin_email_subject) email_context = get_email_context(event=event, order=op.order, position=op) try: if op.attendee_email: op.send_mail( email_subject, email_template, email_context, "pretix_cwa.order.position.email.cwa", ) else: op.order.send_mail( email_subject, email_template, email_context, "pretix_cwa.order.email.cwa", ) except SendMailException: logger.exception("CWA reminder email could not be sent")
def send_voucher(self, quota_cache=None, user=None, auth=None): availability = (self.variation.check_quotas(count_waitinglist=False, subevent=self.subevent, _cache=quota_cache) if self.variation else self.item.check_quotas( count_waitinglist=False, subevent=self.subevent, _cache=quota_cache)) if availability[1] is None or availability[1] < 1: raise WaitingListException( _('This product is currently not available.')) if self.voucher: raise WaitingListException( _('A voucher has already been sent to this person.')) if '@' not in self.email: raise WaitingListException( _('This entry is anonymized and can no longer be used.')) with transaction.atomic(): v = Voucher.objects.create( event=self.event, max_usages=1, valid_until=now() + timedelta(hours=self.event.settings.waiting_list_hours), item=self.item, variation=self.variation, tag='waiting-list', comment=_( 'Automatically created from waiting list entry for {email}' ).format(email=self.email), block_quota=True, subevent=self.subevent, ) v.log_action( 'pretix.voucher.added.waitinglist', { 'item': self.item.pk, 'variation': self.variation.pk if self.variation else None, 'tag': 'waiting-list', 'block_quota': True, 'valid_until': v.valid_until.isoformat(), 'max_usages': 1, 'email': self.email, 'waitinglistentry': self.pk, 'subevent': self.subevent.pk if self.subevent else None, }, user=user, auth=auth) self.log_action('pretix.waitinglist.voucher', user=user, auth=auth) self.voucher = v self.save() with language(self.locale): mail(self.email, _('You have been selected from the waitinglist for {event}'). format(event=str(self.event)), self.event.settings.mail_text_waiting_list, get_email_context(event=self.event, waiting_list_entry=self), self.event, locale=self.locale)
def notify_incomplete_payment(o: Order): with language(o.locale, o.event.settings.region): email_template = o.event.settings.mail_text_order_expire_warning email_context = get_email_context(event=o.event, order=o) email_subject = gettext('Your order received an incomplete payment: %(code)s') % {'code': o.code} try: o.send_mail( email_subject, email_template, email_context, 'pretix.event.order.email.expire_warning_sent' ) except SendMailException: logger.exception('Reminder email could not be sent')
def _send_wle_mail(wle: WaitingListEntry, subject: LazyI18nString, message: LazyI18nString, subevent: SubEvent): with language(wle.locale, wle.event.settings.region): email_context = get_email_context(event_or_subevent=subevent or wle.event, event=wle.event) try: mail(wle.email, str(subject).format_map(TolerantDict(email_context)), message, email_context, wle.event, locale=wle.locale) except SendMailException: logger.exception('Waiting list canceled email could not be sent')
def vouchers_send(event: Event, vouchers: list, subject: str, message: str, recipients: list, user: int, progress=None) -> None: vouchers = list(Voucher.objects.filter(id__in=vouchers).order_by('id')) user = User.objects.get(pk=user) for ir, r in enumerate(recipients): voucher_list = [] for i in range(r['number']): voucher_list.append(vouchers.pop()) with language(event.settings.locale): email_context = get_email_context(event=event, name=r.get('name') or '', voucher_list=[v.code for v in voucher_list]) mail( r['email'], subject, LazyI18nString(message), email_context, event, locale=event.settings.locale, ) logs = [] for v in voucher_list: if r.get('tag') and r.get('tag') != v.tag: v.tag = r.get('tag') if v.comment: v.comment += '\n\n' v.comment = gettext('The voucher has been sent to {recipient}.').format(recipient=r['email']) logs.append(v.log_action( 'pretix.voucher.sent', user=user, data={ 'recipient': r['email'], 'name': r.get('name'), 'subject': subject, 'message': message, }, save=False )) Voucher.objects.bulk_update(voucher_list, fields=['comment', 'tag'], batch_size=500) LogEntry.objects.bulk_create(logs, batch_size=500) if progress and ir % 50 == 0: progress(ir / len(recipients))
def test_mail_context(event, order): with scopes_disabled(): op_date = now().date() remind_after = now() op = orderpayment(order, date=op_date, reminded=False, remind_after=remind_after, old_format=False) ctx = get_email_context(event=event, order=order, sepadebit_payment=op) assert ctx['due_date'] == op_date assert ctx['account_holder'] == "Testaccount" assert ctx['bic'] == "BYLADEM1001" assert ctx['iban'] == "DE02xxxx2051" assert ctx['reference'] == "TESTREF-123" assert ctx['debit_amount'] == "11.00" assert ctx['debit_amount_with_currency'] == "€11.00" assert ctx[ 'creditor_id'] == event.settings.sepadebit_payment__creditor_id assert ctx[ 'creditor_name'] == event.settings.sepadebit_payment__creditor_name
def vouchers_send(event: Event, vouchers: list, subject: str, message: str, recipients: list, user: int) -> None: vouchers = list(Voucher.objects.filter(id__in=vouchers).order_by('id')) user = User.objects.get(pk=user) for r in recipients: voucher_list = [] for i in range(r['number']): voucher_list.append(vouchers.pop()) with language(event.settings.locale): email_context = get_email_context( event=event, name=r.get('name') or '', voucher_list=[v.code for v in voucher_list]) mail( r['email'], subject, LazyI18nString(message), email_context, event, locale=event.settings.locale, ) for v in voucher_list: if r.get('tag') and r.get('tag') != v.tag: v.tag = r.get('tag') if v.comment: v.comment += '\n\n' v.comment = gettext('The voucher has been sent to {recipient}.' ).format(recipient=r['email']) v.save(update_fields=['tag', 'comment']) v.log_action('pretix.voucher.sent', user=user, data={ 'recipient': r['email'], 'name': r.get('name'), 'subject': subject, 'message': message, })
def send_mails(event: Event, user: int, subject: dict, message: dict, orders: list, items: list, recipients: str, filter_checkins: bool, not_checked_in: bool, checkin_lists: list, attachments: list = None, attach_tickets: bool = False) -> None: failures = [] user = User.objects.get(pk=user) if user else None orders = Order.objects.filter(pk__in=orders, event=event) subject = LazyI18nString(subject) message = LazyI18nString(message) for o in orders: send_to_order = recipients in ('both', 'orders') try: ia = o.invoice_address except InvoiceAddress.DoesNotExist: ia = InvoiceAddress(order=o) if recipients in ('both', 'attendees'): for p in o.positions.prefetch_related('addons'): if p.addon_to_id is not None: continue if p.item_id not in items and not any(a.item_id in items for a in p.addons.all()): continue if filter_checkins: checkins = list(p.checkins.all()) allowed = ((not_checked_in and not checkins) or (any(c.list_id in checkin_lists for c in checkins))) if not allowed: continue if not p.attendee_email: if recipients == 'attendees': send_to_order = True continue if p.attendee_email == o.email and send_to_order: continue try: with language(o.locale, event.settings.region): email_context = get_email_context( event=event, order=o, position_or_address=p, position=p) mail(p.attendee_email, subject, message, email_context, event, locale=o.locale, order=o, position=p, attach_tickets=attach_tickets, attach_cached_files=attachments) o.log_action( 'pretix.plugins.sendmail.order.email.sent.attendee', user=user, data={ 'position': p.positionid, 'subject': subject.localize( o.locale).format_map(email_context), 'message': message.localize( o.locale).format_map(email_context), 'recipient': p.attendee_email }) except SendMailException: failures.append(p.attendee_email) if send_to_order and o.email: try: with language(o.locale, event.settings.region): email_context = get_email_context(event=event, order=o, position_or_address=ia) mail(o.email, subject, message, email_context, event, locale=o.locale, order=o, attach_tickets=attach_tickets, attach_cached_files=attachments) o.log_action('pretix.plugins.sendmail.order.email.sent', user=user, data={ 'subject': subject.localize( o.locale).format_map(email_context), 'message': message.localize( o.locale).format_map(email_context), 'recipient': o.email }) except SendMailException: failures.append(o.email)
def send(self): if self.state not in (ScheduledMail.STATE_SCHEDULED, ScheduledMail.STATE_FAILED): raise ValueError("Should not be called in this state") e = self.event orders = e.orders.all() limit_products = self.rule.limit_products.values_list( 'pk', flat=True) if not self.rule.all_products else None if self.subevent: orders = orders.filter( Exists( OrderPosition.objects.filter(order=OuterRef('pk'), subevent=self.subevent))) if not self.rule.all_products: orders = orders.filter( Exists( OrderPosition.objects.filter(order=OuterRef('pk'), item_id__in=limit_products))) status = [Order.STATUS_PENDING, Order.STATUS_PAID ] if self.rule.include_pending else [Order.STATUS_PAID] if self.last_successful_order_id: orders = orders.filter(pk__gt=self.last_successful_order_id) orders = orders.filter( status__in=status, require_approval=False, ).order_by('pk').select_related('invoice_address').prefetch_related( 'positions') send_to_orders = self.rule.send_to in (Rule.CUSTOMERS, Rule.BOTH) send_to_attendees = self.rule.send_to in (Rule.ATTENDEES, Rule.BOTH) for o in orders: positions = list(o.positions.all()) o_sent = False try: ia = o.invoice_address except InvoiceAddress.DoesNotExist: ia = InvoiceAddress(order=o) if send_to_orders and o.email: email_ctx = get_email_context(event=e, order=o, position_or_address=ia) try: o.send_mail( self.rule.subject, self.rule.template, email_ctx, log_entry_type= 'pretix.plugins.sendmail.rule.order.email.sent') o_sent = True except SendMailException: ... # ¯\_(ツ)_/¯ if send_to_attendees: if not self.rule.all_products: positions = [ p for p in positions if p.item_id in limit_products ] if self.subevent_id: positions = [ p for p in positions if p.subevent_id == self.subevent_id ] for p in positions: email_ctx = get_email_context(event=e, order=o, position_or_address=ia, position=p) try: if p.attendee_email and (p.attendee_email != o.email or not o_sent): p.send_mail( self.rule.subject, self.rule.template, email_ctx, log_entry_type= 'pretix.plugins.sendmail.rule.order.position.email.sent' ) elif not o_sent and o.email: o.send_mail( self.rule.subject, self.rule.template, email_ctx, log_entry_type= 'pretix.plugins.sendmail.rule.order.email.sent' ) o_sent = True except SendMailException: ... # ¯\_(ツ)_/¯ self.last_successful_order_id = o.pk
def schedule_second_dose(self, event, op): op = OrderPosition.objects.select_related("item", "variation", "subevent", "order").get(pk=op) if LinkedOrderPosition.objects.filter(base_position=op).exists(): return itemconf = op.item.vacc_autosched_config earliest_date = make_aware( datetime.combine( op.subevent.date_from.astimezone(event.timezone).date() + timedelta(days=itemconf.days), op.subevent.date_from.astimezone(event.timezone).time(), ), event.timezone, ) target_event = itemconf.event or event possible_items = [ n for n in target_event.items.all() if (n.internal_name or str(n.name)) == ( op.item.internal_name or str(op.item.name)) ] if len(possible_items) != 1: op.order.log_action( "pretix_vacc_autosched.failed", data={ "reason": _("No product found"), "position": op.pk, }, ) return target_item = possible_items[0] if op.variation or target_item.variations.exists(): possible_variations = [ n for n in target_item.variations.all() if str(n.value) == ( str(op.variation.value) if op.variation else None) ] if len(possible_variations) != 1: op.order.log_action( "pretix_vacc_autosched.failed", data={ "reason": _("No product variation found"), "position": op.pk, }, ) return target_var = possible_variations[0] else: target_var = None for i in range(100): # max number of subevents to check subevent = (target_event.subevents.filter( date_from__gte=earliest_date, ).order_by("date_from").first()) if not subevent: op.order.log_action( "pretix_vacc_autosched.failed", data={ "reason": _("No available time slot found"), "position": op.pk, }, ) return try: with target_event.lock(), transaction.atomic(): avcode, avnr = target_item.check_quotas(subevent=subevent, fail_on_no_quotas=True) if avcode != Quota.AVAILABILITY_OK: # sold out, look for next one earliest_date += timedelta(minutes=1) continue childorder = Order.objects.create( event=target_event, status=Order.STATUS_PAID, require_approval=False, testmode=op.order.testmode, email=op.order.email, locale=op.order.locale, expires=now() + timedelta(days=30), total=Decimal("0.00"), expiry_reminder_sent=True, sales_channel=op.order.sales_channel, comment="Auto-generated through scheduling from order {}". format(op.order.code), meta_info=op.order.meta_info, ) op.order.log_action( "pretix_vacc_autosched.scheduled", data={ "position": op.pk, "event": target_event.pk, "event_slug": target_event.slug, "order": childorder.code, }, ) childorder.log_action( "pretix_vacc_autosched.created", data={ "event": event.pk, "event_slug": event.slug, "order": op.order.code, }, ) childorder.log_action("pretix.event.order.placed", data={"source": "vacc_autosched"}) childpos = childorder.positions.create( positionid=1, tax_rate=Decimal("0.00"), tax_rule=None, tax_value=Decimal("0.00"), subevent=subevent, item=target_item, variation=target_var, price=Decimal("0.00"), attendee_name_cached=op.attendee_name_cached, attendee_name_parts=op.attendee_name_parts, attendee_email=op.attendee_email, company=op.company, street=op.street, zipcode=op.zipcode, city=op.city, country=op.country, state=op.state, addon_to=None, voucher=None, meta_info=op.meta_info, ) for answ in op.answers.all(): q = childorder.event.questions.filter( identifier=answ.question.identifier).first() if not q: continue childansw = childpos.answers.create(question=q, answer=answ.answer, file=answ.file) if answ.options.all(): childopts = list( q.options.filter(identifier__in=[ o.identifier for o in answ.options.all() ])) if childopts: childansw.options.add(*childopts) LinkedOrderPosition.objects.create(base_position=op, child_position=childpos) order_placed.send(event, order=childorder) order_paid.send(event, order=childorder) if event.settings.vacc_autosched_mail: with language(childorder.locale, target_event.settings.region): email_template = event.settings.vacc_autosched_body email_subject = str(event.settings.vacc_autosched_subject) email_context = get_email_context(event=childorder.event, order=childorder) email_context["scheduled_datetime"] = date_format( subevent.date_from.astimezone(event.timezone), "SHORT_DATETIME_FORMAT", ) try: childorder.send_mail( email_subject, email_template, email_context, "pretix.event.order.email.order_placed", attach_tickets=True, attach_ical=target_event.settings.mail_attach_ical, ) except SendMailException: logger.exception( "Order approved email could not be sent") except LockTimeoutException: self.retry() return op.order.log_action( "pretix_vacc_autosched.failed", data={ "reason": _("No available time slot found"), "position": op.pk, }, ) return
def get_private_icals(event, positions): """ Return a list of ical objects based on a sequence of positions. Unlike get_public_ical, this will - Generate multiple ical files instead of one (but with deduplication applied) - Respect the mail_attach_ical_description setting It is private in the sense that mail_attach_ical_description may contain content not suited for public display. We however intentionally do not allow using placeholders based on the order and position specifically. This is for two reasons: - In reality, many people will add their invite to their calendar which is shared with a larger team. People are probably not aware that they're sharing sensitive information such as their secret ticket link with everyone they share their calendar with. - It would be pretty hard to implement it in a way that doesn't require us to use distinct settings fields for emails to customers and to attendees, which feels like an overcomplication. """ from pretix.base.services.mail import TolerantDict tz = pytz.timezone(event.settings.timezone) creation_time = datetime.datetime.now(pytz.utc) calobjects = [] evs = set(p.subevent or event for p in positions) for ev in evs: if isinstance(ev, Event): url = build_absolute_uri(event, 'presale:event.index') else: url = build_absolute_uri(event, 'presale:event.index', { 'subevent': ev.pk }) if event.settings.mail_attach_ical_description: ctx = get_email_context(event=event, event_or_subevent=ev) description = str(event.settings.mail_attach_ical_description).format_map(TolerantDict(ctx)) else: # Default description descr = [] descr.append(_('Tickets: {url}').format(url=url)) if ev.date_admission: descr.append(str(_('Admission: {datetime}')).format( datetime=date_format(ev.date_admission.astimezone(tz), 'SHORT_DATETIME_FORMAT') )) descr.append(_('Organizer: {organizer}').format(organizer=event.organizer.name)) description = '\n'.join(descr) cal = vobject.iCalendar() cal.add('prodid').value = '-//pretix//{}//'.format(settings.PRETIX_INSTANCE_NAME.replace(" ", "_")) vevent = cal.add('vevent') vevent.add('summary').value = str(ev.name) vevent.add('description').value = description vevent.add('dtstamp').value = creation_time if ev.location: vevent.add('location').value = str(ev.location) vevent.add('uid').value = 'pretix-{}-{}-{}@{}'.format( event.organizer.slug, event.organizer.slug, event.slug, ev.pk if not isinstance(ev, Event) else '0', urlparse(url).netloc ) if event.settings.show_times: vevent.add('dtstart').value = ev.date_from.astimezone(tz) else: vevent.add('dtstart').value = ev.date_from.astimezone(tz).date() if event.settings.show_date_to and ev.date_to: if event.settings.show_times: vevent.add('dtend').value = ev.date_to.astimezone(tz) else: # with full-day events date_to in pretix is included (e.g. last day) # whereas dtend in vcalendar is non-inclusive => add one day for export vevent.add('dtend').value = ev.date_to.astimezone(tz).date() + datetime.timedelta(days=1) calobjects.append(cal) return calobjects
def book_second_dose(*, op, item, variation, subevent, original_event): event = item.event with event.lock(), transaction.atomic(): avcode, avnr = item.check_quotas(subevent=subevent, fail_on_no_quotas=True) if avcode != Quota.AVAILABILITY_OK: logger.info( f"SECOND DOSE: cannot use slot {subevent.pk}, sold out") # sold out, look for next one return childorder = Order.objects.create( event=event, status=Order.STATUS_PAID, require_approval=False, testmode=op.order.testmode, email=op.order.email, phone=op.order.phone, locale=op.order.locale, expires=now() + timedelta(days=30), total=Decimal("0.00"), expiry_reminder_sent=True, sales_channel=op.order.sales_channel, comment="Auto-generated through scheduling from order {}".format( op.order.code), meta_info=op.order.meta_info, ) op.order.log_action( "pretix_vacc_autosched.scheduled", data={ "position": op.pk, "event": event.pk, "event_slug": event.slug, "order": childorder.code, }, ) childorder.log_action( "pretix_vacc_autosched.created", data={ "event": original_event.pk, "event_slug": original_event.slug, "order": op.order.code, }, ) childorder.log_action("pretix.event.order.placed", data={"source": "vacc_autosched"}) childpos = childorder.positions.create( positionid=1, tax_rate=Decimal("0.00"), tax_rule=None, tax_value=Decimal("0.00"), subevent=subevent, item=item, variation=variation, price=Decimal("0.00"), attendee_name_cached=op.attendee_name_cached, attendee_name_parts=op.attendee_name_parts, attendee_email=op.attendee_email, company=op.company, street=op.street, zipcode=op.zipcode, city=op.city, country=op.country, state=op.state, addon_to=None, voucher=None, meta_info=op.meta_info, ) for answ in op.answers.all(): q = childorder.event.questions.filter( identifier=answ.question.identifier).first() if not q: continue childansw = childpos.answers.create(question=q, answer=answ.answer, file=answ.file) if answ.options.all(): childopts = list( q.options.filter(identifier__in=[ o.identifier for o in answ.options.all() ])) if childopts: childansw.options.add(*childopts) LinkedOrderPosition.objects.create(base_position=op, child_position=childpos) childorder.create_transactions(is_new=True) order_placed.send(event, order=childorder) order_paid.send(event, order=childorder) if original_event.settings.vacc_autosched_mail: with language(childorder.locale, original_event.settings.region): email_template = original_event.settings.vacc_autosched_body email_subject = str(original_event.settings.vacc_autosched_subject) email_context = get_email_context(event=childorder.event, order=childorder) email_context["scheduled_datetime"] = date_format( subevent.date_from.astimezone(original_event.timezone), "SHORT_DATETIME_FORMAT", ) try: childorder.send_mail( email_subject, email_template, email_context, "pretix.event.order.email.order_placed", attach_tickets=True, attach_ical=event.settings.mail_attach_ical, ) except SendMailException: logger.exception("Order approved email could not be sent") if (original_event.settings.vacc_autosched_sms and can_use_juvare_api(original_event) and childorder.phone): with language(childorder.locale, original_event.settings.region): from pretix_juvare_notify.tasks import juvare_send_text template = original_event.settings.vacc_autosched_sms_text context = get_email_context(event=childorder.event, order=childorder) context["scheduled_datetime"] = date_format( subevent.date_from.astimezone(original_event.timezone), "SHORT_DATETIME_FORMAT", ) message = str(template).format_map(TolerantDict(context)) juvare_send_text.apply_async( kwargs={ "text": message, "to": childorder.phone, "event": original_event.pk, }) logger.info(f"SECOND DOSE: done, created order {childorder.code}") return childorder