Attach receipt PDF to "payment successful" email #96850
@ -26,7 +26,7 @@ from looper.admin import (
|
|||||||
ACCOUNT_INFORMATION_FIELDSET,
|
ACCOUNT_INFORMATION_FIELDSET,
|
||||||
)
|
)
|
||||||
from looper.admin.filters import EmptyFieldListFilter
|
from looper.admin.filters import EmptyFieldListFilter
|
||||||
import blender_fund_main.email as email
|
import blender_fund_main.email
|
||||||
from . import models, forms
|
from . import models, forms
|
||||||
import looper.admin.reports
|
import looper.admin.reports
|
||||||
import looper.forms
|
import looper.forms
|
||||||
@ -464,7 +464,7 @@ class _RenderEmailPreview:
|
|||||||
"""Render the "html_message" attribute of this object."""
|
"""Render the "html_message" attribute of this object."""
|
||||||
iframe_template = Template(
|
iframe_template = Template(
|
||||||
'<iframe sandbox="allow-same-origin" style="min-width: 40vw;" srcdoc="{{ body|safe }}"'
|
'<iframe sandbox="allow-same-origin" style="min-width: 40vw;" srcdoc="{{ body|safe }}"'
|
||||||
'onload="this.style.height=(this.contentWindow.document.body.scrollHeight + 16)+\'px\';">'
|
'onload="this.style.height=(this.contentWindow.document.body.scrollHeight + 16)+\'px\';">' # noqa: E501
|
||||||
'</iframe>'
|
'</iframe>'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -603,7 +603,7 @@ class AutomaticPaymentEmailPreviewAdmin(
|
|||||||
|
|
||||||
mail_name = object_id
|
mail_name = object_id
|
||||||
objects_q = looper.models.Order.objects
|
objects_q = looper.models.Order.objects
|
||||||
_template_function = email.construct_payment_mail
|
_template_function = blender_fund_main.email.construct_payment_mail
|
||||||
if mail_name == 'payment_paid':
|
if mail_name == 'payment_paid':
|
||||||
objects_q = objects_q.filter(status='paid')
|
objects_q = objects_q.filter(status='paid')
|
||||||
elif mail_name == 'payment_soft-failed':
|
elif mail_name == 'payment_soft-failed':
|
||||||
@ -612,7 +612,7 @@ class AutomaticPaymentEmailPreviewAdmin(
|
|||||||
objects_q = objects_q.filter(status='failed')
|
objects_q = objects_q.filter(status='failed')
|
||||||
elif mail_name == 'donation_received':
|
elif mail_name == 'donation_received':
|
||||||
objects_q = objects_q.filter(status='paid', subscription__isnull=True)
|
objects_q = objects_q.filter(status='paid', subscription__isnull=True)
|
||||||
_template_function = email.construct_donation_received_mail
|
_template_function = blender_fund_main.email.construct_donation_received_mail
|
||||||
else:
|
else:
|
||||||
raise Exception(f'Unknown payment notification email {mail_name}')
|
raise Exception(f'Unknown payment notification email {mail_name}')
|
||||||
obj = default_obj = objects_q.filter(customer__user__isnull=False).first()
|
obj = default_obj = objects_q.filter(customer__user__isnull=False).first()
|
||||||
@ -666,9 +666,9 @@ class MembershipChangedEmailPreviewAdmin(
|
|||||||
"""Construct the Email on th fly from known subscription email templates."""
|
"""Construct the Email on th fly from known subscription email templates."""
|
||||||
|
|
||||||
mail_name = object_id
|
mail_name = object_id
|
||||||
_template_function = email._construct_membership_mail
|
_template_function = blender_fund_main.email.construct_membership_mail
|
||||||
if mail_name == 'managed_memb_notif':
|
if mail_name == 'managed_memb_notif':
|
||||||
_template_function = email._construct_managed_subscription_mail
|
_template_function = blender_fund_main.email.construct_managed_subscription_mail
|
||||||
|
|
||||||
objects_q = models.Membership.objects
|
objects_q = models.Membership.objects
|
||||||
if mail_name == 'membership_activated':
|
if mail_name == 'membership_activated':
|
||||||
|
@ -8,4 +8,4 @@ class BlenderFundMainConfig(AppConfig):
|
|||||||
def ready(self):
|
def ready(self):
|
||||||
# Import modules to register signal handlers.
|
# Import modules to register signal handlers.
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
from . import email, signals
|
from . import signals
|
||||||
|
@ -2,72 +2,20 @@ import logging
|
|||||||
import typing
|
import typing
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.sites.shortcuts import get_current_site
|
|
||||||
import django.core.mail
|
|
||||||
from django.dispatch import receiver
|
|
||||||
from django.template import loader
|
from django.template import loader
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
import django.core.mail
|
||||||
|
|
||||||
from . import models, signals
|
from . import models
|
||||||
from looper.pdf import PDFResponse
|
|
||||||
import looper.models
|
import looper.models
|
||||||
import looper.signals
|
import looper.signals
|
||||||
|
|
||||||
|
from blender_fund_main.utils import absolute_url, is_noreply
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def absolute_url(viewname: str,
|
def construct_membership_mail(membership: models.Membership) -> typing.Tuple[str, str, str]:
|
||||||
args: typing.Optional[tuple] = None,
|
|
||||||
kwargs: typing.Optional[dict] = None) -> str:
|
|
||||||
"""Same as django.urls.reverse() but then as absolute URL.
|
|
||||||
|
|
||||||
For simplicity this assumes HTTPS is used.
|
|
||||||
"""
|
|
||||||
from urllib.parse import urljoin
|
|
||||||
|
|
||||||
domain = get_current_site(None).domain
|
|
||||||
relative_url = reverse(viewname, args=args, kwargs=kwargs)
|
|
||||||
return urljoin(f'https://{domain}/', relative_url)
|
|
||||||
|
|
||||||
|
|
||||||
def is_noreply(email: str) -> bool:
|
|
||||||
"""Return True if the email address is a no-reply address."""
|
|
||||||
return email.startswith('noreply@') or email.startswith('no-reply@')
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(signals.membership_activated)
|
|
||||||
@receiver(signals.membership_cancelled)
|
|
||||||
@receiver(signals.membership_created_needs_payment)
|
|
||||||
def send_mail_membership_status_changed(sender: models.Membership, **kwargs):
|
|
||||||
"""Send out an email notifying about changed status of a membership."""
|
|
||||||
email = sender.customer.billing_address.email
|
|
||||||
if is_noreply(email):
|
|
||||||
log.debug('Not sending membership-changed notification to no-reply address %s', email)
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
email_body_html, email_body_txt, subject = _construct_membership_mail(membership=sender)
|
|
||||||
|
|
||||||
log.debug('Sending membership-changed notification to %s', email)
|
|
||||||
|
|
||||||
django.core.mail.send_mail(
|
|
||||||
subject,
|
|
||||||
message=email_body_txt,
|
|
||||||
html_message=email_body_html,
|
|
||||||
from_email=None, # just use the configured default From-address.
|
|
||||||
recipient_list=[email],
|
|
||||||
fail_silently=False,
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
# Template rendering errors shouldn't interfere with the Looper clock, so
|
|
||||||
# catch all errors here.
|
|
||||||
log.exception('Error sending membership-changed %r notification to %s',
|
|
||||||
sender.status, email)
|
|
||||||
else:
|
|
||||||
log.info('Sent membership-changed notification to %s', email)
|
|
||||||
|
|
||||||
|
|
||||||
def _construct_membership_mail(membership: models.Membership) -> typing.Tuple[str, str, str]:
|
|
||||||
"""Construct a mail about a membership.
|
"""Construct a mail about a membership.
|
||||||
|
|
||||||
:return: tuple (html, text, subject)
|
:return: tuple (html, text, subject)
|
||||||
@ -120,51 +68,11 @@ def _construct_membership_mail(membership: models.Membership) -> typing.Tuple[st
|
|||||||
return email_body_html, email_body_txt, context['subject']
|
return email_body_html, email_body_txt, context['subject']
|
||||||
|
|
||||||
|
|
||||||
@receiver(looper.signals.automatic_payment_succesful)
|
|
||||||
@receiver(looper.signals.automatic_payment_soft_failed)
|
|
||||||
@receiver(looper.signals.automatic_payment_failed)
|
|
||||||
def automatic_payment_performed(sender: looper.models.Order,
|
|
||||||
transaction: looper.models.Transaction,
|
|
||||||
**kwargs):
|
|
||||||
"""Send out an email notifying about the status of an automated payment."""
|
|
||||||
email = sender.email
|
|
||||||
if is_noreply(email):
|
|
||||||
log.debug('Not sending payment notification to no-reply address %s', email)
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
email_body_html, email_body_txt, subject = construct_payment_mail(
|
|
||||||
sender=sender, transaction=transaction
|
|
||||||
)
|
|
||||||
|
|
||||||
log.debug('Sending payment %r notification to %s', sender.status, email)
|
|
||||||
|
|
||||||
msg = django.core.mail.EmailMultiAlternatives(
|
|
||||||
subject=subject,
|
|
||||||
body=email_body_txt,
|
|
||||||
from_email=None, # just use the configured default From-address.
|
|
||||||
to=[email],
|
|
||||||
)
|
|
||||||
file_data = b'TODO'
|
|
||||||
file_name = f'blender-development-fund-receipt-{sender.display_number}.pdf'
|
|
||||||
print(file_name)
|
|
||||||
file_data = PDFResponse().rendered_content
|
|
||||||
msg.attach(file_name, file_data, 'application/pdf')
|
|
||||||
msg.attach_alternative(email_body_html, 'text/html')
|
|
||||||
msg.send(fail_silently=False)
|
|
||||||
except Exception:
|
|
||||||
# Template rendering errors shouldn't interfere with the Looper clock, so
|
|
||||||
# catch all errors here.
|
|
||||||
log.exception('Error sending payment %r notification to %s',
|
|
||||||
sender.status, email)
|
|
||||||
else:
|
|
||||||
log.info('Sent %r notification to %s', sender.status, email)
|
|
||||||
|
|
||||||
|
|
||||||
def construct_payment_mail(
|
def construct_payment_mail(
|
||||||
sender: looper.models.Order, transaction: looper.models.Transaction
|
order: looper.models.Order, transaction: looper.models.Transaction
|
||||||
) -> typing.Tuple[str, str, str]:
|
) -> typing.Tuple[str, str, str]:
|
||||||
membership = sender.subscription.membership
|
membership = order.subscription.membership
|
||||||
customer = sender.customer
|
customer = order.customer
|
||||||
user = customer.user
|
user = customer.user
|
||||||
|
|
||||||
membership_url = link_membership_url = None
|
membership_url = link_membership_url = None
|
||||||
@ -177,15 +85,15 @@ def construct_payment_mail(
|
|||||||
membership_url = absolute_url(
|
membership_url = absolute_url(
|
||||||
'settings_membership_edit', kwargs={'membership_id': membership.pk}
|
'settings_membership_edit', kwargs={'membership_id': membership.pk}
|
||||||
)
|
)
|
||||||
pay_url = absolute_url('looper:checkout_existing_order', kwargs={'order_id': sender.pk})
|
pay_url = absolute_url('looper:checkout_existing_order', kwargs={'order_id': order.pk})
|
||||||
receipt_url = absolute_url('settings_receipt', kwargs={'order_id': sender.pk})
|
receipt_url = absolute_url('settings_receipt', kwargs={'order_id': order.pk})
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'user': user,
|
'user': user,
|
||||||
'customer': customer,
|
'customer': customer,
|
||||||
'full_name': customer.billing_address.full_name,
|
'full_name': customer.billing_address.full_name,
|
||||||
'email': customer.billing_address.email,
|
'email': customer.billing_address.email,
|
||||||
'order': sender,
|
'order': order,
|
||||||
'membership': membership,
|
'membership': membership,
|
||||||
'pay_url': pay_url,
|
'pay_url': pay_url,
|
||||||
'receipt_url': receipt_url,
|
'receipt_url': receipt_url,
|
||||||
@ -197,23 +105,23 @@ def construct_payment_mail(
|
|||||||
}
|
}
|
||||||
|
|
||||||
subject: str = loader.render_to_string(
|
subject: str = loader.render_to_string(
|
||||||
f'blender_fund_main/emails/payment_{sender.status}_subject.txt', context).strip()
|
f'blender_fund_main/emails/payment_{order.status}_subject.txt', context).strip()
|
||||||
context['subject'] = subject
|
context['subject'] = subject
|
||||||
|
|
||||||
email_body_html = loader.render_to_string(
|
email_body_html = loader.render_to_string(
|
||||||
f'blender_fund_main/emails/payment_{sender.status}.html', context)
|
f'blender_fund_main/emails/payment_{order.status}.html', context)
|
||||||
email_body_txt = loader.render_to_string(
|
email_body_txt = loader.render_to_string(
|
||||||
f'blender_fund_main/emails/payment_{sender.status}.txt', context)
|
f'blender_fund_main/emails/payment_{order.status}.txt', context)
|
||||||
|
|
||||||
return email_body_html, email_body_txt, context['subject']
|
return email_body_html, email_body_txt, context['subject']
|
||||||
|
|
||||||
|
|
||||||
def _construct_managed_subscription_mail(sender: looper.models.Subscription):
|
def construct_managed_subscription_mail(subscription: looper.models.Subscription):
|
||||||
customer = sender.customer
|
customer = subscription.customer
|
||||||
user = customer.user
|
user = customer.user
|
||||||
membership = sender.membership
|
membership = subscription.membership
|
||||||
subs_admin_url = absolute_url('admin:looper_subscription_change',
|
subs_admin_url = absolute_url('admin:looper_subscription_change',
|
||||||
kwargs={'object_id': sender.id})
|
kwargs={'object_id': subscription.id})
|
||||||
memb_admin_url = absolute_url('admin:blender_fund_main_membership_change',
|
memb_admin_url = absolute_url('admin:blender_fund_main_membership_change',
|
||||||
kwargs={'object_id': membership.id})
|
kwargs={'object_id': membership.id})
|
||||||
context = {
|
context = {
|
||||||
@ -221,7 +129,7 @@ def _construct_managed_subscription_mail(sender: looper.models.Subscription):
|
|||||||
'customer': customer,
|
'customer': customer,
|
||||||
'full_name': settings.LOOPER_MANAGER_MAIL,
|
'full_name': settings.LOOPER_MANAGER_MAIL,
|
||||||
'email': settings.LOOPER_MANAGER_MAIL,
|
'email': settings.LOOPER_MANAGER_MAIL,
|
||||||
'subscription': sender,
|
'subscription': subscription,
|
||||||
'membership': membership,
|
'membership': membership,
|
||||||
'subs_admin_url': subs_admin_url,
|
'subs_admin_url': subs_admin_url,
|
||||||
'memb_admin_url': memb_admin_url,
|
'memb_admin_url': memb_admin_url,
|
||||||
@ -239,44 +147,6 @@ def _construct_managed_subscription_mail(sender: looper.models.Subscription):
|
|||||||
return email_body_html, email_body_txt, context['subject']
|
return email_body_html, email_body_txt, context['subject']
|
||||||
|
|
||||||
|
|
||||||
@receiver(looper.signals.managed_subscription_notification)
|
|
||||||
def managed_subscription_notification(sender: looper.models.Subscription,
|
|
||||||
**kwargs):
|
|
||||||
"""Send out an email notifying a manager about an expiring managed subscription."""
|
|
||||||
my_log = log.getChild('managed_subscription_notification')
|
|
||||||
email = settings.LOOPER_MANAGER_MAIL
|
|
||||||
membership = sender.membership
|
|
||||||
my_log.debug('Notifying %s about managed subscription %r passing its next_payment date',
|
|
||||||
email, sender.pk)
|
|
||||||
|
|
||||||
try:
|
|
||||||
email_body_html, email_body_txt, subject = _construct_managed_subscription_mail(
|
|
||||||
sender=sender
|
|
||||||
)
|
|
||||||
except Exception as ex:
|
|
||||||
# Template rendering errors shouldn't interfere with the Looper clock, so
|
|
||||||
# catch all errors here.
|
|
||||||
my_log.exception('Error rendering templates to send notification about managed '
|
|
||||||
'membership %r to %s: %s', membership.pk, email, ex)
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
django.core.mail.send_mail(
|
|
||||||
subject,
|
|
||||||
message=email_body_txt,
|
|
||||||
html_message=email_body_html,
|
|
||||||
from_email=None, # just use the configured default From-address.
|
|
||||||
recipient_list=[email],
|
|
||||||
fail_silently=False,
|
|
||||||
)
|
|
||||||
except OSError as ex:
|
|
||||||
my_log.exception('Error sending notification mail about managed '
|
|
||||||
'membership %r to %s: %s', membership.pk, email, ex)
|
|
||||||
else:
|
|
||||||
my_log.info('Notified %s about managed subscription %r passing its next_payment date',
|
|
||||||
email, sender.pk)
|
|
||||||
|
|
||||||
|
|
||||||
def construct_donation_received_mail(order):
|
def construct_donation_received_mail(order):
|
||||||
customer = order.customer
|
customer = order.customer
|
||||||
user = customer.user
|
user = customer.user
|
||||||
|
@ -13,6 +13,9 @@ import looper.signals
|
|||||||
import looper.models
|
import looper.models
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
import blender_fund_main.tasks as tasks
|
||||||
|
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -344,3 +347,26 @@ def _grant_campaign_badges(sender, instance: models.Campaign, **kwargs):
|
|||||||
if action != 'post_add' or not pk_set:
|
if action != 'post_add' or not pk_set:
|
||||||
return
|
return
|
||||||
instance.grant_badges(order_pks=pk_set)
|
instance.grant_badges(order_pks=pk_set)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(membership_activated)
|
||||||
|
@receiver(membership_cancelled)
|
||||||
|
@receiver(membership_created_needs_payment)
|
||||||
|
def _on_membership_status_changed(sender: models.Membership, **kwargs):
|
||||||
|
tasks.send_mail_membership_status_changed(membership_id=sender.pk)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(looper.signals.automatic_payment_succesful)
|
||||||
|
@receiver(looper.signals.automatic_payment_soft_failed)
|
||||||
|
@receiver(looper.signals.automatic_payment_failed)
|
||||||
|
def _on_automatic_payment_performed(
|
||||||
|
sender: looper.models.Order,
|
||||||
|
transaction: looper.models.Transaction,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
tasks.send_mail_automatic_payment_performed(order_id=sender.pk, transaction_id=transaction.pk)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(looper.signals.managed_subscription_notification)
|
||||||
|
def _on_managed_subscription_notification(sender: looper.models.Subscription, **kwargs):
|
||||||
|
tasks.send_mail_managed_subscription_notification(subscription_id=sender.pk)
|
||||||
|
@ -6,18 +6,28 @@ import sys
|
|||||||
from background_task import background
|
from background_task import background
|
||||||
from background_task.models import Task
|
from background_task.models import Task
|
||||||
from background_task.tasks import TaskSchedule
|
from background_task.tasks import TaskSchedule
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.db.transaction import atomic
|
from django.db.transaction import atomic
|
||||||
|
import django.core.mail
|
||||||
|
|
||||||
import looper.stripe_utils
|
from looper.pdf import PDFResponse
|
||||||
from looper.stripe_utils import (
|
from looper.stripe_utils import (
|
||||||
upsert_order_from_payment_intent_and_product,
|
upsert_order_from_payment_intent_and_product,
|
||||||
upsert_subscription_payment_method_from_setup_intent,
|
upsert_subscription_payment_method_from_setup_intent,
|
||||||
)
|
)
|
||||||
import looper.models
|
import looper.models
|
||||||
|
import looper.stripe_utils
|
||||||
import stripe
|
import stripe
|
||||||
|
|
||||||
import blender_fund_main.email as email
|
from blender_fund_main.utils import is_noreply
|
||||||
|
from blender_fund_main.email import (
|
||||||
|
construct_managed_subscription_mail,
|
||||||
|
construct_membership_mail,
|
||||||
|
construct_payment_mail,
|
||||||
|
donation_received,
|
||||||
|
)
|
||||||
|
import blender_fund_main.models
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -62,7 +72,7 @@ def handle_payment_intent_succeeded(payload: str):
|
|||||||
)
|
)
|
||||||
new_status = order.status
|
new_status = order.status
|
||||||
if order.subscription is None and old_status != new_status and new_status == 'paid':
|
if order.subscription is None and old_status != new_status and new_status == 'paid':
|
||||||
email.donation_received(order)
|
donation_received(order)
|
||||||
|
|
||||||
|
|
||||||
@background()
|
@background()
|
||||||
@ -106,3 +116,89 @@ def grant_or_revoke_badge(user_id: int, grant: str = '', revoke: str = ''):
|
|||||||
return
|
return
|
||||||
user = User.objects.get(pk=user_id)
|
user = User.objects.get(pk=user_id)
|
||||||
badges.change_badge(user=user, revoke=revoke, grant=grant)
|
badges.change_badge(user=user, revoke=revoke, grant=grant)
|
||||||
|
|
||||||
|
|
||||||
|
@background(schedule={'action': TaskSchedule.RESCHEDULE_EXISTING})
|
||||||
|
def send_mail_automatic_payment_performed(order_id: int, transaction_id: int):
|
||||||
|
"""Send out an email notifying about the status of an automated payment."""
|
||||||
|
order = looper.models.Order.objects.get(pk=order_id)
|
||||||
|
transaction = looper.models.Transaction.objects.get(pk=transaction_id)
|
||||||
|
email = order.email
|
||||||
|
if is_noreply(email):
|
||||||
|
logger.debug('Not sending payment notification to no-reply address %s', email)
|
||||||
|
return
|
||||||
|
|
||||||
|
email_body_html, email_body_txt, subject = construct_payment_mail(
|
||||||
|
order=order, transaction=transaction
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.debug('Sending payment %r notification to %s', order.status, email)
|
||||||
|
msg = django.core.mail.EmailMultiAlternatives(
|
||||||
|
subject=subject,
|
||||||
|
body=email_body_txt,
|
||||||
|
from_email=None, # just use the configured default From-address.
|
||||||
|
to=[email],
|
||||||
|
)
|
||||||
|
file_data = b'TODO'
|
||||||
|
file_name = f'blender-development-fund-receipt-{order.display_number}.pdf'
|
||||||
|
print(file_name)
|
||||||
|
file_data = PDFResponse().rendered_content
|
||||||
|
msg.attach(file_name, file_data, 'application/pdf')
|
||||||
|
msg.attach_alternative(email_body_html, 'text/html')
|
||||||
|
msg.send(fail_silently=False)
|
||||||
|
logger.info('Sent %r notification to %s', order.status, email)
|
||||||
|
|
||||||
|
|
||||||
|
@background(schedule={'action': TaskSchedule.RESCHEDULE_EXISTING})
|
||||||
|
def send_mail_membership_status_changed(membership_id: int):
|
||||||
|
"""Send out an email notifying about changed status of a membership."""
|
||||||
|
membership = blender_fund_main.models.Membership.objects.get(pk=membership_id)
|
||||||
|
email = membership.customer.billing_address.email
|
||||||
|
if is_noreply(email):
|
||||||
|
logger.debug('Not sending membership-changed notification to no-reply address %s', email)
|
||||||
|
return
|
||||||
|
|
||||||
|
email_body_html, email_body_txt, subject = construct_membership_mail(
|
||||||
|
membership=membership
|
||||||
|
)
|
||||||
|
logger.debug('Sending membership-changed notification to %s', email)
|
||||||
|
|
||||||
|
django.core.mail.send_mail(
|
||||||
|
subject,
|
||||||
|
message=email_body_txt,
|
||||||
|
html_message=email_body_html,
|
||||||
|
from_email=None, # just use the configured default From-address.
|
||||||
|
recipient_list=[email],
|
||||||
|
fail_silently=False,
|
||||||
|
)
|
||||||
|
logger.info('Sent membership-changed notification to %s', email)
|
||||||
|
|
||||||
|
|
||||||
|
@background(schedule={'action': TaskSchedule.RESCHEDULE_EXISTING})
|
||||||
|
def send_mail_managed_subscription_notification(subscription_id: int):
|
||||||
|
"""Send out an email notifying a manager about an expiring managed subscription."""
|
||||||
|
subscription = looper.models.Subscription.objects.get(pk=subscription_id)
|
||||||
|
email = settings.LOOPER_MANAGER_MAIL
|
||||||
|
logger.debug(
|
||||||
|
'Notifying %s about managed subscription %r passing its next_payment date',
|
||||||
|
email,
|
||||||
|
subscription_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
email_body_html, email_body_txt, subject = construct_managed_subscription_mail(
|
||||||
|
subscription=subscription
|
||||||
|
)
|
||||||
|
|
||||||
|
django.core.mail.send_mail(
|
||||||
|
subject,
|
||||||
|
message=email_body_txt,
|
||||||
|
html_message=email_body_html,
|
||||||
|
from_email=None, # just use the configured default From-address.
|
||||||
|
recipient_list=[email],
|
||||||
|
fail_silently=False,
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
'Notified %s about managed subscription %r passing its next_payment date',
|
||||||
|
email,
|
||||||
|
subscription_id,
|
||||||
|
)
|
||||||
|
@ -1,13 +1,183 @@
|
|||||||
import django.core.mail
|
from unittest.mock import patch
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
import django.core.mail
|
||||||
|
|
||||||
from blender_fund_main.email import absolute_url
|
|
||||||
from looper.models import Subscription
|
from looper.models import Subscription
|
||||||
from looper.tests import AbstractBaseTestCase
|
from looper.tests import AbstractBaseTestCase
|
||||||
|
|
||||||
from ..models import Membership
|
from ..models import Membership
|
||||||
|
from blender_fund_main.utils import absolute_url, html_to_text
|
||||||
|
import blender_fund_main.tasks as tasks
|
||||||
|
|
||||||
|
expected_mail_body = '''Dear Erik von Namenstein,
|
||||||
|
|
||||||
|
Thank you for joining the Blender Development Fund. You chose to pay
|
||||||
|
for your Silver membership by bank transfer, which
|
||||||
|
means that we will be waiting for you to perform this transfer.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Your membership will be activated as soon as we have handled your bank transfer.
|
||||||
|
When paying, please mention the following:
|
||||||
|
|
||||||
|
Blender Fund Membership subs-1
|
||||||
|
|
||||||
|
Please send your payment of € 10.00 to:
|
||||||
|
|
||||||
|
Stichting Blender Foundation
|
||||||
|
Buikslotermeerplein 161
|
||||||
|
1025 ET Amsterdam, the Netherlands
|
||||||
|
|
||||||
|
Bank: ING Bank
|
||||||
|
Bijlmerdreef 109
|
||||||
|
1102 BW Amsterdam, the Netherlands
|
||||||
|
|
||||||
|
BIC/Swift code: INGB NL 2A
|
||||||
|
IBAN: NL45 INGB 0009356121 (for Euro countries)
|
||||||
|
|
||||||
|
Tax number NL 8111.66.223
|
||||||
|
|
||||||
|
You can always go to https://example.com/settings/membership/1 to view and update your membership.
|
||||||
|
|
||||||
|
--
|
||||||
|
Kind regards,
|
||||||
|
Blender Foundation
|
||||||
|
'''
|
||||||
|
expected_mail_html_text = '''Blender Development Fund membership Bank Payment
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Dear Erik von Namenstein,
|
||||||
|
|
||||||
|
|
||||||
|
Thank you for joining the Blender Development Fund. You chose to pay
|
||||||
|
for your Silver membership by bank transfer, which
|
||||||
|
means that we will be waiting for you to perform this transfer.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Your membership will be activated as soon as we have handled your bank transfer.
|
||||||
|
When paying, please mention the following:
|
||||||
|
|
||||||
|
Blender Fund Membership subs-1
|
||||||
|
Please send your payment of € 10.00 to:
|
||||||
|
|
||||||
|
Stichting Blender Foundation
|
||||||
|
Buikslotermeerplein 161
|
||||||
|
1025 ET Amsterdam, the Netherlands
|
||||||
|
|
||||||
|
Bank: ING Bank
|
||||||
|
Bijlmerdreef 109
|
||||||
|
1102 BW Amsterdam, the Netherlands
|
||||||
|
|
||||||
|
BIC/Swift code: INGB NL 2A
|
||||||
|
IBAN: NL45 INGB 0009356121 (for Euro countries)
|
||||||
|
|
||||||
|
Tax number NL 8111.66.223
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
You can always go to https://example.com/settings/membership/1 to view and update your membership.
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
Kind regards,
|
||||||
|
Blender Foundation'''
|
||||||
|
expected_mail_w_token_body = '''Dear Erik von Namenstein,
|
||||||
|
|
||||||
|
Thank you for joining the Blender Development Fund. You chose to pay
|
||||||
|
for your Silver membership by bank transfer, which
|
||||||
|
means that we will be waiting for you to perform this transfer.
|
||||||
|
|
||||||
|
|
||||||
|
One more step: **to manage or cancel this membership** and claim your Silver badge,
|
||||||
|
follow the link below and sign in with your Blender ID.
|
||||||
|
|
||||||
|
https://example.com/link-membership/{token}/
|
||||||
|
|
||||||
|
Your membership will be activated as soon as we have handled your bank transfer.
|
||||||
|
When paying, please mention the following:
|
||||||
|
|
||||||
|
Blender Fund Membership subs-1
|
||||||
|
|
||||||
|
Please send your payment of € 10.00 to:
|
||||||
|
|
||||||
|
Stichting Blender Foundation
|
||||||
|
Buikslotermeerplein 161
|
||||||
|
1025 ET Amsterdam, the Netherlands
|
||||||
|
|
||||||
|
Bank: ING Bank
|
||||||
|
Bijlmerdreef 109
|
||||||
|
1102 BW Amsterdam, the Netherlands
|
||||||
|
|
||||||
|
BIC/Swift code: INGB NL 2A
|
||||||
|
IBAN: NL45 INGB 0009356121 (for Euro countries)
|
||||||
|
|
||||||
|
Tax number NL 8111.66.223
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
Kind regards,
|
||||||
|
Blender Foundation
|
||||||
|
'''
|
||||||
|
expected_mail_w_token_html_text = '''Blender Development Fund membership Bank Payment
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Dear Erik von Namenstein,
|
||||||
|
|
||||||
|
|
||||||
|
Thank you for joining the Blender Development Fund. You chose to pay
|
||||||
|
for your Silver membership by bank transfer, which
|
||||||
|
means that we will be waiting for you to perform this transfer.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
One more step: to manage or cancel this membership and claim your Silver badge,
|
||||||
|
follow the link below and sign in with your Blender ID.
|
||||||
|
|
||||||
|
|
||||||
|
https://example.com/link-membership/{token}/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Your membership will be activated as soon as we have handled your bank transfer.
|
||||||
|
When paying, please mention the following:
|
||||||
|
|
||||||
|
Blender Fund Membership subs-1
|
||||||
|
Please send your payment of € 10.00 to:
|
||||||
|
|
||||||
|
Stichting Blender Foundation
|
||||||
|
Buikslotermeerplein 161
|
||||||
|
1025 ET Amsterdam, the Netherlands
|
||||||
|
|
||||||
|
Bank: ING Bank
|
||||||
|
Bijlmerdreef 109
|
||||||
|
1102 BW Amsterdam, the Netherlands
|
||||||
|
|
||||||
|
BIC/Swift code: INGB NL 2A
|
||||||
|
IBAN: NL45 INGB 0009356121 (for Euro countries)
|
||||||
|
|
||||||
|
Tax number NL 8111.66.223
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
Kind regards,
|
||||||
|
Blender Foundation'''
|
||||||
|
|
||||||
|
|
||||||
|
@patch(
|
||||||
|
'blender_fund_main.tasks.send_mail_membership_status_changed',
|
||||||
|
new=tasks.send_mail_membership_status_changed.task_function,
|
||||||
|
)
|
||||||
class CheckoutTestCase(AbstractBaseTestCase):
|
class CheckoutTestCase(AbstractBaseTestCase):
|
||||||
fixtures = ['gateways', 'devfund', 'systemuser']
|
fixtures = ['gateways', 'devfund', 'systemuser']
|
||||||
|
|
||||||
@ -60,6 +230,12 @@ class CheckoutTestCase(AbstractBaseTestCase):
|
|||||||
self.assertIn(edit_url, the_mail.body)
|
self.assertIn(edit_url, the_mail.body)
|
||||||
alt0_body, alt0_type = the_mail.alternatives[0]
|
alt0_body, alt0_type = the_mail.alternatives[0]
|
||||||
self.assertEqual(alt0_type, 'text/html')
|
self.assertEqual(alt0_type, 'text/html')
|
||||||
|
self.assertEqual(the_mail.body, expected_mail_body, the_mail.body)
|
||||||
|
self.assertEqual(
|
||||||
|
html_to_text(alt0_body),
|
||||||
|
expected_mail_html_text,
|
||||||
|
html_to_text(the_mail.alternatives[0][0]),
|
||||||
|
)
|
||||||
|
|
||||||
def test_bank_creates_inactive_membership_sends_email_without_an_account(self):
|
def test_bank_creates_inactive_membership_sends_email_without_an_account(self):
|
||||||
membership, the_mail = self._test_bank_creates_inactive_membership()
|
membership, the_mail = self._test_bank_creates_inactive_membership()
|
||||||
@ -71,8 +247,21 @@ class CheckoutTestCase(AbstractBaseTestCase):
|
|||||||
kwargs={'membership_id': membership.id})
|
kwargs={'membership_id': membership.id})
|
||||||
self.assertNotIn(edit_url, the_mail.body)
|
self.assertNotIn(edit_url, the_mail.body)
|
||||||
# but claim link should
|
# but claim link should
|
||||||
|
token = membership.customer.token.token
|
||||||
link_membership_url = absolute_url(
|
link_membership_url = absolute_url(
|
||||||
'link_membership',
|
'link_membership',
|
||||||
kwargs={'token': membership.customer.token.token},
|
kwargs={'token': token},
|
||||||
)
|
)
|
||||||
self.assertIn(link_membership_url, the_mail.body)
|
self.assertIn(link_membership_url, the_mail.body)
|
||||||
|
self.assertEqual(
|
||||||
|
the_mail.body,
|
||||||
|
expected_mail_w_token_body.format(token=token),
|
||||||
|
the_mail.body,
|
||||||
|
)
|
||||||
|
alt0_body, alt0_type = the_mail.alternatives[0]
|
||||||
|
self.assertEqual(alt0_type, 'text/html')
|
||||||
|
self.assertEqual(
|
||||||
|
html_to_text(alt0_body),
|
||||||
|
expected_mail_w_token_html_text.format(token=token),
|
||||||
|
html_to_text(alt0_body),
|
||||||
|
)
|
||||||
|
@ -15,6 +15,7 @@ from looper.tests.factories import SubscriptionFactory, create_customer_with_bil
|
|||||||
import looper.exceptions
|
import looper.exceptions
|
||||||
|
|
||||||
from blender_fund_main.utils import html_to_text
|
from blender_fund_main.utils import html_to_text
|
||||||
|
import blender_fund_main.tasks as tasks
|
||||||
|
|
||||||
|
|
||||||
expected_managed_email_subj = 'Blender Development Fund managed membership needs attention'
|
expected_managed_email_subj = 'Blender Development Fund managed membership needs attention'
|
||||||
@ -68,7 +69,7 @@ You can always go to https://fund.local:8010/settings/membership/1 to view and u
|
|||||||
--
|
--
|
||||||
Kind regards,
|
Kind regards,
|
||||||
Blender Foundation
|
Blender Foundation
|
||||||
'''
|
''' # noqa: E501
|
||||||
expected_soft_failed_email_html_stripped = '''Blender Development Fund: payment failed (but we'll try again)
|
expected_soft_failed_email_html_stripped = '''Blender Development Fund: payment failed (but we'll try again)
|
||||||
|
|
||||||
|
|
||||||
@ -99,7 +100,7 @@ You can always go to https://fund.local:8010/settings/membership/1 to view and u
|
|||||||
|
|
||||||
--
|
--
|
||||||
Kind regards,
|
Kind regards,
|
||||||
Blender Foundation'''
|
Blender Foundation''' # noqa: E501
|
||||||
expected_failed_email_body = '''Dear Jane Doe,
|
expected_failed_email_body = '''Dear Jane Doe,
|
||||||
|
|
||||||
|
|
||||||
@ -116,7 +117,7 @@ You can always go to https://fund.local:8010/settings/membership/1 to view and u
|
|||||||
--
|
--
|
||||||
Kind regards,
|
Kind regards,
|
||||||
Blender Foundation
|
Blender Foundation
|
||||||
'''
|
''' # noqa: E501
|
||||||
expected_failed_email_html_stripped = '''Blender Development Fund: payment failed
|
expected_failed_email_html_stripped = '''Blender Development Fund: payment failed
|
||||||
|
|
||||||
|
|
||||||
@ -144,7 +145,7 @@ You can always go to https://fund.local:8010/settings/membership/1 to view and u
|
|||||||
|
|
||||||
--
|
--
|
||||||
Kind regards,
|
Kind regards,
|
||||||
Blender Foundation'''
|
Blender Foundation''' # noqa: E501
|
||||||
expected_payment_received_email_body = '''Dear Jane Doe,
|
expected_payment_received_email_body = '''Dear Jane Doe,
|
||||||
|
|
||||||
Automatic payment of your Blender Development Fund membership (€ 10.00)
|
Automatic payment of your Blender Development Fund membership (€ 10.00)
|
||||||
@ -203,7 +204,17 @@ def _write_mail(mail):
|
|||||||
f.write(str(content))
|
f.write(str(content))
|
||||||
|
|
||||||
|
|
||||||
|
@patch(
|
||||||
|
'blender_fund_main.tasks.send_mail_automatic_payment_performed',
|
||||||
|
new=tasks.send_mail_automatic_payment_performed.task_function,
|
||||||
|
)
|
||||||
|
@patch(
|
||||||
|
'blender_fund_main.tasks.send_mail_managed_subscription_notification',
|
||||||
|
new=tasks.send_mail_managed_subscription_notification.task_function,
|
||||||
|
)
|
||||||
class TestClockEmails(AbstractLooperTestCase):
|
class TestClockEmails(AbstractLooperTestCase):
|
||||||
|
fixtures = AbstractLooperTestCase.fixtures + ['default_site']
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.subscription = self._create_membership_due_now()
|
self.subscription = self._create_membership_due_now()
|
||||||
@ -286,8 +297,8 @@ class TestClockEmails(AbstractLooperTestCase):
|
|||||||
customer = self.subscription.customer
|
customer = self.subscription.customer
|
||||||
user = customer.user
|
user = customer.user
|
||||||
_write_mail(mail)
|
_write_mail(mail)
|
||||||
self.assertEqual(len(mail.outbox), 2)
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
email = mail.outbox[-1]
|
email = mail.outbox[0]
|
||||||
self.assertEqual(email.to, [user.customer.billing_address.email])
|
self.assertEqual(email.to, [user.customer.billing_address.email])
|
||||||
# TODO(anna): set the correct reply_to
|
# TODO(anna): set the correct reply_to
|
||||||
self.assertEqual(email.reply_to, [])
|
self.assertEqual(email.reply_to, [])
|
||||||
@ -351,9 +362,9 @@ class TestClockEmails(AbstractLooperTestCase):
|
|||||||
# Check that an email notification is sent
|
# Check that an email notification is sent
|
||||||
customer = self.subscription.customer
|
customer = self.subscription.customer
|
||||||
user = customer.user
|
user = customer.user
|
||||||
self.assertEqual(len(mail.outbox), 2)
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
_write_mail(mail)
|
_write_mail(mail)
|
||||||
email = mail.outbox[-1]
|
email = mail.outbox[0]
|
||||||
self.assertEqual(email.to, [user.customer.billing_address.email])
|
self.assertEqual(email.to, [user.customer.billing_address.email])
|
||||||
# TODO(anna): set the correct reply_to
|
# TODO(anna): set the correct reply_to
|
||||||
self.assertEqual(email.reply_to, [])
|
self.assertEqual(email.reply_to, [])
|
||||||
@ -412,8 +423,8 @@ class TestClockEmails(AbstractLooperTestCase):
|
|||||||
customer = self.subscription.customer
|
customer = self.subscription.customer
|
||||||
user = customer.user
|
user = customer.user
|
||||||
_write_mail(mail)
|
_write_mail(mail)
|
||||||
self.assertEqual(len(mail.outbox), 2)
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
email = mail.outbox[-1]
|
email = mail.outbox[0]
|
||||||
self.assertEqual(email.to, [user.customer.billing_address.email])
|
self.assertEqual(email.to, [user.customer.billing_address.email])
|
||||||
# TODO(anna): set the correct reply_to
|
# TODO(anna): set the correct reply_to
|
||||||
self.assertEqual(email.reply_to, [])
|
self.assertEqual(email.reply_to, [])
|
||||||
@ -446,8 +457,8 @@ class TestClockEmails(AbstractLooperTestCase):
|
|||||||
|
|
||||||
# Check that an email notification is sent
|
# Check that an email notification is sent
|
||||||
_write_mail(mail)
|
_write_mail(mail)
|
||||||
self.assertEqual(len(mail.outbox), 2)
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
email = mail.outbox[-1]
|
email = mail.outbox[0]
|
||||||
self.assertEqual(email.to, ['admin@example.com'])
|
self.assertEqual(email.to, ['admin@example.com'])
|
||||||
self.assertEqual(email.from_email, 'webmaster@localhost')
|
self.assertEqual(email.from_email, 'webmaster@localhost')
|
||||||
self.assertEqual(email.subject, expected_managed_email_subj)
|
self.assertEqual(email.subject, expected_managed_email_subj)
|
||||||
|
@ -137,6 +137,8 @@ class LinkMembershipTest(AbstractLooperTestCase):
|
|||||||
self.assertIsNone(order.subscription_id)
|
self.assertIsNone(order.subscription_id)
|
||||||
self.assertEqual(order.customer_id, accountless_customer_id)
|
self.assertEqual(order.customer_id, accountless_customer_id)
|
||||||
some_user = User.objects.create(email='joe@example.com', username='newnickname')
|
some_user = User.objects.create(email='joe@example.com', username='newnickname')
|
||||||
|
tasks_q = background_task.models.Task.objects.order_by('-pk')
|
||||||
|
tasks_before = tasks_q.count()
|
||||||
|
|
||||||
self.client.force_login(some_user)
|
self.client.force_login(some_user)
|
||||||
response = self.client.post(self.url, data={'agree_to_link_membership': True})
|
response = self.client.post(self.url, data={'agree_to_link_membership': True})
|
||||||
@ -148,18 +150,17 @@ class LinkMembershipTest(AbstractLooperTestCase):
|
|||||||
# Successfully claiming a membership should result in a redirect to account settings
|
# Successfully claiming a membership should result in a redirect to account settings
|
||||||
self.subscription.refresh_from_db()
|
self.subscription.refresh_from_db()
|
||||||
|
|
||||||
tasks_q = background_task.models.Task.objects.all()
|
self.assertEqual(tasks_q.count(), tasks_before + 2)
|
||||||
self.assertEqual(tasks_q.count(), 2)
|
|
||||||
# A membership badge was grated to the account based on membership level
|
# A membership badge was grated to the account based on membership level
|
||||||
self.assertEqual(tasks_q[0].task_name, 'blender_fund_main.tasks.grant_or_revoke_badge')
|
|
||||||
self.assertEqual(
|
|
||||||
tasks_q[0].task_params,
|
|
||||||
f'[[], {{"grant": "devfund_gold", "revoke": "", "user_id": {some_user.pk}}}]',
|
|
||||||
)
|
|
||||||
# A campaign badge was grated to the account based on an campaign order
|
|
||||||
self.assertEqual(tasks_q[1].task_name, 'blender_fund_main.tasks.grant_or_revoke_badge')
|
self.assertEqual(tasks_q[1].task_name, 'blender_fund_main.tasks.grant_or_revoke_badge')
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
tasks_q[1].task_params,
|
tasks_q[1].task_params,
|
||||||
|
f'[[], {{"grant": "devfund_gold", "revoke": "", "user_id": {some_user.pk}}}]',
|
||||||
|
)
|
||||||
|
# A campaign badge was grated to the account based on an campaign order
|
||||||
|
self.assertEqual(tasks_q[0].task_name, 'blender_fund_main.tasks.grant_or_revoke_badge')
|
||||||
|
self.assertEqual(
|
||||||
|
tasks_q[0].task_params,
|
||||||
f'[[], {{"grant": "campaign-badge", "user_id": {some_user.pk}}}]',
|
f'[[], {{"grant": "campaign-badge", "user_id": {some_user.pk}}}]',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,11 +1,23 @@
|
|||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
import django.core.mail
|
import django.core.mail
|
||||||
|
|
||||||
from blender_fund_main.email import absolute_url
|
|
||||||
from looper.tests import AbstractLooperTestCase
|
from looper.tests import AbstractLooperTestCase
|
||||||
import looper.signals
|
import looper.signals
|
||||||
|
|
||||||
|
from blender_fund_main.utils import absolute_url
|
||||||
|
import blender_fund_main.tasks as tasks
|
||||||
|
|
||||||
|
|
||||||
|
@patch(
|
||||||
|
'blender_fund_main.tasks.send_mail_managed_subscription_notification',
|
||||||
|
new=tasks.send_mail_managed_subscription_notification.task_function,
|
||||||
|
)
|
||||||
|
@patch(
|
||||||
|
'blender_fund_main.tasks.send_mail_membership_status_changed',
|
||||||
|
new=tasks.send_mail_membership_status_changed.task_function,
|
||||||
|
)
|
||||||
class ManagedMembershipNotificationMailTest(AbstractLooperTestCase):
|
class ManagedMembershipNotificationMailTest(AbstractLooperTestCase):
|
||||||
fixtures = ['default_site', 'gateways', 'devfund', 'testuser', 'systemuser']
|
fixtures = ['default_site', 'gateways', 'devfund', 'testuser', 'systemuser']
|
||||||
|
|
||||||
|
@ -1,15 +1,22 @@
|
|||||||
|
from unittest.mock import patch
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import django.core.mail
|
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
import django.core.mail
|
||||||
|
|
||||||
from looper.tests import AbstractLooperTestCase
|
from looper.tests import AbstractLooperTestCase
|
||||||
from .. import models, signals
|
from .. import models, signals
|
||||||
|
|
||||||
|
import blender_fund_main.tasks as tasks
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@patch(
|
||||||
|
'blender_fund_main.tasks.send_mail_membership_status_changed',
|
||||||
|
new=tasks.send_mail_membership_status_changed.task_function,
|
||||||
|
)
|
||||||
class MembershipActivationSignalTest(AbstractLooperTestCase):
|
class MembershipActivationSignalTest(AbstractLooperTestCase):
|
||||||
fixtures = AbstractLooperTestCase.fixtures + ['default_site', 'systemuser']
|
fixtures = AbstractLooperTestCase.fixtures + ['default_site', 'systemuser']
|
||||||
|
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
import django.core.mail
|
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.test import override_settings
|
import django.core.mail
|
||||||
|
|
||||||
from looper.tests import AbstractBaseTestCase
|
from looper.tests import AbstractBaseTestCase
|
||||||
|
|
||||||
|
import blender_fund_main.tasks as tasks
|
||||||
|
|
||||||
|
|
||||||
|
@patch(
|
||||||
|
'blender_fund_main.tasks.send_mail_membership_status_changed',
|
||||||
|
new=tasks.send_mail_membership_status_changed.task_function,
|
||||||
|
)
|
||||||
class MembershipActivationSignalTest(AbstractBaseTestCase):
|
class MembershipActivationSignalTest(AbstractBaseTestCase):
|
||||||
fixtures = ['default_site', 'gateways', 'devfund']
|
fixtures = ['default_site', 'gateways', 'devfund']
|
||||||
|
|
||||||
|
@ -1,6 +1,32 @@
|
|||||||
|
import typing
|
||||||
|
|
||||||
from html.parser import HTMLParser
|
from html.parser import HTMLParser
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.sites.shortcuts import get_current_site
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
|
||||||
|
def absolute_url(viewname: str,
|
||||||
|
args: typing.Optional[tuple] = None,
|
||||||
|
kwargs: typing.Optional[dict] = None) -> str:
|
||||||
|
"""Same as django.urls.reverse() but then as absolute URL.
|
||||||
|
|
||||||
|
For simplicity this assumes HTTPS is used, unless in DEBUG mode.
|
||||||
|
"""
|
||||||
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
|
proto = 'http' if settings.DEBUG else 'https'
|
||||||
|
domain = get_current_site(None).domain
|
||||||
|
relative_url = reverse(viewname, args=args, kwargs=kwargs)
|
||||||
|
return urljoin(f'{proto}://{domain}/', relative_url)
|
||||||
|
|
||||||
|
|
||||||
|
def is_noreply(email: str) -> bool:
|
||||||
|
"""Return True if the email address is a no-reply address."""
|
||||||
|
return email.startswith('noreply@') or email.startswith('no-reply@')
|
||||||
|
|
||||||
|
|
||||||
class HTMLFilter(HTMLParser):
|
class HTMLFilter(HTMLParser):
|
||||||
skip_text_of = ('a', 'style')
|
skip_text_of = ('a', 'style')
|
||||||
|
Loading…
Reference in New Issue
Block a user