Send notification mails via celery #509

This commit is contained in:
Daniel Grams 2023-06-30 17:44:57 +02:00
parent aad15b4b1f
commit 4e1cff514c
27 changed files with 93 additions and 58 deletions

View File

@ -197,7 +197,7 @@ db = SQLAlchemy(app, metadata=metadata)
migrate = Migrate(app, db, render_as_batch=False) migrate = Migrate(app, db, render_as_batch=False)
# Celery tasks # Celery tasks
from project import celery_tasks from project import base_tasks, celery_tasks
# API # API
from project.api import RestApi from project.api import RestApi

View File

@ -95,7 +95,7 @@ from project.services.reference import (
get_reference_outgoing_query, get_reference_outgoing_query,
get_relation_outgoing_query, get_relation_outgoing_query,
) )
from project.views.utils import get_current_admin_unit_for_api, send_mail from project.views.utils import get_current_admin_unit_for_api, send_mail_async
class OrganizationResource(BaseResource): class OrganizationResource(BaseResource):
@ -451,7 +451,7 @@ class OrganizationOrganizationInvitationListResource(BaseResource):
db.session.add(invitation) db.session.add(invitation)
db.session.commit() db.session.commit()
send_mail( send_mail_async(
invitation.email, invitation.email,
gettext("You have received an invitation"), gettext("You have received an invitation"),
"organization_invitation_notice", "organization_invitation_notice",

View File

@ -1,12 +1,12 @@
from project import app, celery from project import app, celery
from project.celery import force_locale from project.celery import force_locale
from project.views.utils import send_mail from project.views.utils import send_mails_with_body
@celery.task( @celery.task(
base=getattr(app, "celery_http_task_cls"), base=getattr(app, "celery_http_task_cls"),
priority=0, priority=0,
) )
def send_mail_task(recipient, subject, template, **context): def send_mail_with_body_task(recipient, subject, body, html):
with force_locale(): with force_locale():
send_mail(recipient, subject, template, **context) send_mails_with_body([recipient], subject, body, html)

View File

@ -1,4 +1,3 @@
from celery import group
from flask import flash, redirect, render_template, request, url_for from flask import flash, redirect, render_template, request, url_for
from flask_babel import gettext from flask_babel import gettext
from flask_security import roles_required from flask_security import roles_required
@ -6,7 +5,6 @@ from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.sql import func from sqlalchemy.sql import func
from project import app, db from project import app, db
from project.base_tasks import send_mail_task
from project.forms.admin import ( from project.forms.admin import (
AdminNewsletterForm, AdminNewsletterForm,
AdminSettingsForm, AdminSettingsForm,
@ -24,11 +22,12 @@ from project.services.user import delete_user, set_roles_for_user
from project.views.utils import ( from project.views.utils import (
flash_errors, flash_errors,
get_celery_poll_group_result, get_celery_poll_group_result,
get_celery_poll_result,
get_pagination_urls, get_pagination_urls,
handleSqlError, handleSqlError,
non_match_for_deletion, non_match_for_deletion,
send_mail, send_mail,
send_mail_async,
send_mails_async,
) )
@ -148,7 +147,7 @@ def admin_email():
form = AdminTestEmailForm() form = AdminTestEmailForm()
if "poll" in request.args: # pragma: no cover if "poll" in request.args: # pragma: no cover
return get_celery_poll_result() return get_celery_poll_group_result()
if form.validate_on_submit(): if form.validate_on_submit():
subject = gettext( subject = gettext(
@ -157,7 +156,8 @@ def admin_email():
) )
if "async" in request.args: # pragma: no cover if "async" in request.args: # pragma: no cover
result = send_mail_task.delay(form.recipient.data, subject, "test_email") result = send_mail_async(form.recipient.data, subject, "test_email")
result.save()
return {"result_id": result.id} return {"result_id": result.id}
try: try:
@ -196,10 +196,9 @@ def admin_newsletter():
) )
recipients = [u.email for u in users] recipients = [u.email for u in users]
result = group( result = send_mails_async(
send_mail_task.s(r, subject, "newsletter", message=form.message.data) recipients, subject, "newsletter", message=form.message.data
for r in recipients )
).delay()
result.save() result.save()
return {"result_id": result.id} return {"result_id": result.id}

View File

@ -32,7 +32,7 @@ from project.views.utils import (
manage_required, manage_required,
non_match_for_deletion, non_match_for_deletion,
permission_missing, permission_missing,
send_mails, send_mails_async,
) )
@ -260,7 +260,7 @@ def send_admin_unit_invitation_accepted_mails(
) )
emails = list(map(lambda member: member.user.email, members)) emails = list(map(lambda member: member.user.email, members))
send_mails( send_mails_async(
emails, emails,
gettext("Organization invitation accepted"), gettext("Organization invitation accepted"),
"organization_invitation_accepted_notice", "organization_invitation_accepted_notice",
@ -274,7 +274,7 @@ def send_admin_unit_deletion_requested_mails(admin_unit: AdminUnit):
members = get_admin_unit_members_with_permission(admin_unit.id, "admin_unit:update") members = get_admin_unit_members_with_permission(admin_unit.id, "admin_unit:update")
emails = list(map(lambda member: member.user.email, members)) emails = list(map(lambda member: member.user.email, members))
send_mails( send_mails_async(
emails, emails,
gettext("Organization deletion requested"), gettext("Organization deletion requested"),
"organization_deletion_requested_notice", "organization_deletion_requested_notice",

View File

@ -18,7 +18,7 @@ from project.views.utils import (
handleSqlError, handleSqlError,
non_match_for_deletion, non_match_for_deletion,
permission_missing, permission_missing,
send_mail, send_mail_async,
) )
@ -84,7 +84,7 @@ def manage_admin_unit_member_invite(id):
db.session.add(invitation) db.session.add(invitation)
db.session.commit() db.session.commit()
send_mail( send_mail_async(
invitation.email, invitation.email,
gettext("You have received an invitation"), gettext("You have received an invitation"),
"invitation_notice", "invitation_notice",

View File

@ -52,7 +52,7 @@ from project.views.utils import (
get_calendar_links_for_event, get_calendar_links_for_event,
get_share_links, get_share_links,
handleSqlError, handleSqlError,
send_mails, send_mails_async,
set_current_admin_unit, set_current_admin_unit,
) )
@ -452,7 +452,7 @@ def send_referenced_event_changed_mails(event):
) )
emails = list(map(lambda member: member.user.email, members)) emails = list(map(lambda member: member.user.email, members))
send_mails( send_mails_async(
emails, emails,
gettext("Referenced event changed"), gettext("Referenced event changed"),
"referenced_event_changed_notice", "referenced_event_changed_notice",
@ -475,7 +475,7 @@ def send_event_report_mails(event: Event, report: dict):
admin_emails = list(map(lambda admin: admin.email, admins)) admin_emails = list(map(lambda admin: admin.email, admins))
emails.extend(x for x in admin_emails if x not in emails) emails.extend(x for x in admin_emails if x not in emails)
send_mails( send_mails_async(
emails, emails,
gettext("New event report"), gettext("New event report"),
"event_report_notice", "event_report_notice",

View File

@ -7,7 +7,7 @@ from project import app, db
from project.access import access_or_401 from project.access import access_or_401
from project.forms.event_suggestion import RejectEventSuggestionForm from project.forms.event_suggestion import RejectEventSuggestionForm
from project.models import EventReviewStatus, EventSuggestion from project.models import EventReviewStatus, EventSuggestion
from project.views.utils import flash_errors, handleSqlError, send_mail from project.views.utils import flash_errors, handleSqlError, send_mail_async
@app.route("/event_suggestion/<int:event_suggestion_id>/review") @app.route("/event_suggestion/<int:event_suggestion_id>/review")
@ -82,7 +82,7 @@ def event_suggestion_review_status(event_suggestion_id):
def send_event_suggestion_review_status_mail(event_suggestion): def send_event_suggestion_review_status_mail(event_suggestion):
if event_suggestion.contact_email and event_suggestion.contact_email_notice: if event_suggestion.contact_email and event_suggestion.contact_email_notice:
send_mail( send_mail_async(
event_suggestion.contact_email, event_suggestion.contact_email,
gettext("Event review status updated"), gettext("Event review status updated"),
"review_status_notice", "review_status_notice",

View File

@ -29,7 +29,7 @@ from project.views.utils import (
flash_errors, flash_errors,
get_pagination_urls, get_pagination_urls,
handleSqlError, handleSqlError,
send_mails, send_mails_async,
) )
@ -159,7 +159,7 @@ def send_member_reference_request_verify_mails(
) )
emails = list(map(lambda member: member.user.email, members)) emails = list(map(lambda member: member.user.email, members))
send_mails(emails, subject, template, **context) send_mails_async(emails, subject, template, **context)
def send_reference_request_inbox_mails(request): def send_reference_request_inbox_mails(request):

View File

@ -21,7 +21,7 @@ from project.services.admin_unit import (
upsert_admin_unit_relation, upsert_admin_unit_relation,
) )
from project.services.reference import create_event_reference_for_request from project.services.reference import create_event_reference_for_request
from project.views.utils import flash_errors, handleSqlError, send_mails from project.views.utils import flash_errors, handleSqlError, send_mails_async
@app.route("/reference_request/<int:id>/review", methods=("GET", "POST")) @app.route("/reference_request/<int:id>/review", methods=("GET", "POST"))
@ -130,7 +130,7 @@ def send_reference_request_review_status_mails(request):
) )
emails = list(map(lambda member: member.user.email, members)) emails = list(map(lambda member: member.user.email, members))
send_mails( send_mails_async(
emails, emails,
gettext("Event review status updated"), gettext("Event review status updated"),
"reference_request_review_status_notice", "reference_request_review_status_notice",

View File

@ -20,7 +20,7 @@ from project.views.utils import (
get_invitation_access_result, get_invitation_access_result,
handleSqlError, handleSqlError,
non_match_for_deletion, non_match_for_deletion,
send_mail, send_mail_async,
) )
@ -155,7 +155,7 @@ def user_cancel_deletion():
def send_user_deletion_requested_mail(user): def send_user_deletion_requested_mail(user):
send_mail( send_mail_async(
user.email, user.email,
gettext("User deletion requested"), gettext("User deletion requested"),
"user_deletion_requested_notice", "user_deletion_requested_notice",

View File

@ -168,9 +168,37 @@ def send_mails(recipients, subject, template, **context):
if len(recipients) == 0: # pragma: no cover if len(recipients) == 0: # pragma: no cover
return return
body, html = render_mail_body(template, **context)
send_mails_with_body(recipients, subject, body, html)
def send_mail_async(recipient, subject, template, **context):
send_mails_async([recipient], subject, template, **context)
def send_mails_async(recipients, subject, template, **context):
if len(recipients) == 0: # pragma: no cover
return
body, html = render_mail_body(template, **context)
return send_mails_with_body_async(recipients, subject, body, html)
def send_mails_with_body_async(recipients, subject, body, html):
from celery import group
from project.base_tasks import send_mail_with_body_task
result = group(
send_mail_with_body_task.s(r, subject, body, html) for r in recipients
).delay()
return result
def render_mail_body(template, **context):
body = render_template("email/%s.txt" % template, **context) body = render_template("email/%s.txt" % template, **context)
html = render_template("email/%s.html" % template, **context) html = render_template("email/%s.html" % template, **context)
send_mails_with_body(recipients, subject, body, html) return body, html
def send_mails_with_body(recipients, subject, body, html): def send_mails_with_body(recipients, subject, body, html):

View File

@ -22,7 +22,7 @@ from project.views.utils import (
handleSqlError, handleSqlError,
manage_required, manage_required,
non_match_for_deletion, non_match_for_deletion,
send_mails, send_mails_async,
) )
@ -192,7 +192,7 @@ def send_member_verification_request_verify_mails(
) )
emails = list(map(lambda member: member.user.email, members)) emails = list(map(lambda member: member.user.email, members))
send_mails(emails, subject, template, **context) send_mails_async(emails, subject, template, **context)
def send_verification_request_inbox_mails(request): def send_verification_request_inbox_mails(request):

View File

@ -15,7 +15,7 @@ from project.models import (
AdminUnitVerificationRequestReviewStatus, AdminUnitVerificationRequestReviewStatus,
) )
from project.services.admin_unit import upsert_admin_unit_relation from project.services.admin_unit import upsert_admin_unit_relation
from project.views.utils import flash_errors, handleSqlError, send_mails from project.views.utils import flash_errors, handleSqlError, send_mails_async
@app.route("/verification_request/<int:id>/review", methods=("GET", "POST")) @app.route("/verification_request/<int:id>/review", methods=("GET", "POST"))
@ -110,7 +110,7 @@ def send_verification_request_review_status_mails(request):
) )
emails = list(map(lambda member: member.user.email, members)) emails = list(map(lambda member: member.user.email, members))
send_mails( send_mails_async(
emails, emails,
gettext("Verification request review status updated"), gettext("Verification request review status updated"),
"verification_request_review_status_notice", "verification_request_review_status_notice",

View File

@ -30,7 +30,7 @@ from project.views.utils import (
get_pagination_urls, get_pagination_urls,
get_share_links, get_share_links,
handleSqlError, handleSqlError,
send_mails, send_mails_async,
) )
@ -214,7 +214,7 @@ def send_event_inbox_mails(admin_unit, event_suggestion):
members = get_admin_unit_members_with_permission(admin_unit.id, "event:verify") members = get_admin_unit_members_with_permission(admin_unit.id, "event:verify")
emails = list(map(lambda member: member.user.email, members)) emails = list(map(lambda member: member.user.email, members))
send_mails( send_mails_async(
emails, emails,
gettext("New event review"), gettext("New event review"),
"review_notice", "review_notice",

View File

@ -542,7 +542,7 @@ def test_put_referencedEventUpdate_sendsMail(
other_admin_unit_id = seeder.create_admin_unit(other_user_id, "Other Crew") other_admin_unit_id = seeder.create_admin_unit(other_user_id, "Other Crew")
seeder.create_reference(event_id, other_admin_unit_id) seeder.create_reference(event_id, other_admin_unit_id)
mail_mock = utils.mock_send_mails(mocker) mail_mock = utils.mock_send_mails_async(mocker)
url = utils.get_url("api_v1_event", id=event_id) url = utils.get_url("api_v1_event", id=event_id)
put = create_put(place_id, organizer_id) put = create_put(place_id, organizer_id)
put["name"] = "Changed name" put["name"] = "Changed name"
@ -564,7 +564,7 @@ def test_put_referencedEventNonDirtyUpdate_doesNotSendMail(
other_admin_unit_id = seeder.create_admin_unit(other_user_id, "Other Crew") other_admin_unit_id = seeder.create_admin_unit(other_user_id, "Other Crew")
seeder.create_reference(event_id, other_admin_unit_id) seeder.create_reference(event_id, other_admin_unit_id)
mail_mock = utils.mock_send_mails(mocker) mail_mock = utils.mock_send_mails_async(mocker)
url = utils.get_url("api_v1_event", id=event_id) url = utils.get_url("api_v1_event", id=event_id)
put = create_put(place_id, organizer_id) put = create_put(place_id, organizer_id)
put["name"] = "Name" put["name"] = "Name"
@ -617,7 +617,7 @@ def test_patch_referencedEventUpdate_sendsMail(
other_admin_unit_id = seeder.create_admin_unit(other_user_id, "Other Crew") other_admin_unit_id = seeder.create_admin_unit(other_user_id, "Other Crew")
seeder.create_reference(event_id, other_admin_unit_id) seeder.create_reference(event_id, other_admin_unit_id)
mail_mock = utils.mock_send_mails(mocker) mail_mock = utils.mock_send_mails_async(mocker)
url = utils.get_url("api_v1_event", id=event_id) url = utils.get_url("api_v1_event", id=event_id)
response = utils.patch_json(url, {"name": "Changed name"}) response = utils.patch_json(url, {"name": "Changed name"})
@ -716,7 +716,7 @@ def test_report_mail(client, seeder: Seeder, utils: UtilActions, app, mocker):
seeder.create_user(email="admin@test.de", admin=True) seeder.create_user(email="admin@test.de", admin=True)
seeder.create_user(email="normal@test.de", admin=False) seeder.create_user(email="normal@test.de", admin=False)
mail_mock = utils.mock_send_mails(mocker) mail_mock = utils.mock_send_mails_async(mocker)
url = utils.get_url("api_v1_event_reports", id=event_id) url = utils.get_url("api_v1_event_reports", id=event_id)
response = utils.post_json( response = utils.post_json(
url, url,

View File

@ -625,7 +625,7 @@ def test_organization_invitation_list(client, seeder: Seeder, utils: UtilActions
def test_organization_invitation_list_post(client, app, seeder, db, utils, mocker): def test_organization_invitation_list_post(client, app, seeder, db, utils, mocker):
mail_mock = utils.mock_send_mails(mocker) mail_mock = utils.mock_send_mails_async(mocker)
_, admin_unit_id = seeder.setup_api_access() _, admin_unit_id = seeder.setup_api_access()
url = utils.get_url( url = utils.get_url(

View File

@ -204,6 +204,9 @@ class UtilActions(object):
def mock_send_mails(self, mocker): def mock_send_mails(self, mocker):
return mocker.patch("project.views.utils.send_mails_with_body") return mocker.patch("project.views.utils.send_mails_with_body")
def mock_send_mails_async(self, mocker):
return mocker.patch("project.views.utils.send_mails_with_body_async")
def assert_send_mail_called( def assert_send_mail_called(
self, mock, expected_recipients, expected_contents=None self, mock, expected_recipients, expected_contents=None
): ):

View File

@ -138,7 +138,7 @@ def test_create_requiresAdmin_memberOfOrgWithFlag(client, app, utils, seeder):
def test_create_from_invitation(client, app, db, utils, seeder, mocker): def test_create_from_invitation(client, app, db, utils, seeder, mocker):
mail_mock = utils.mock_send_mails(mocker) mail_mock = utils.mock_send_mails_async(mocker)
user_id = seeder.create_user() user_id = seeder.create_user()
admin_unit_id = seeder.create_admin_unit( admin_unit_id = seeder.create_admin_unit(
user_id, can_invite_other=True, can_verify_other=True user_id, can_invite_other=True, can_verify_other=True

View File

@ -11,7 +11,7 @@ def test_create(client, app, utils, seeder, mocker):
assert response.status_code == 200 assert response.status_code == 200
with client: with client:
mail_mock = utils.mock_send_mails(mocker) mail_mock = utils.mock_send_mails_async(mocker)
email = "new@member.de" email = "new@member.de"
response = client.post( response = client.post(
url, url,

View File

@ -50,7 +50,7 @@ def test_reject(client, app, utils, seeder, mocker, db, db_error, is_verified):
if db_error: if db_error:
utils.mock_db_commit(mocker) utils.mock_db_commit(mocker)
mail_mock = utils.mock_send_mails(mocker) mail_mock = utils.mock_send_mails_async(mocker)
response = utils.post_form( response = utils.post_form(
url, url,
response, response,

View File

@ -197,7 +197,7 @@ def test_referencedEventUpdate_sendsMail(client, seeder, utils, app, mocker):
url = utils.get_url("event_update", event_id=event_id) url = utils.get_url("event_update", event_id=event_id)
response = utils.get_ok(url) response = utils.get_ok(url)
mail_mock = utils.mock_send_mails(mocker) mail_mock = utils.mock_send_mails_async(mocker)
response = utils.post_form( response = utils.post_form(
url, url,
response, response,
@ -226,7 +226,7 @@ def test_referencedEventNonDirtyUpdate_doesNotSendMail(
url = utils.get_url("event_update", event_id=event_id) url = utils.get_url("event_update", event_id=event_id)
response = utils.get_ok(url) response = utils.get_ok(url)
mail_mock = utils.mock_send_mails(mocker) mail_mock = utils.mock_send_mails_async(mocker)
response = utils.post_form( response = utils.post_form(
url, url,
response, response,

View File

@ -1,8 +1,11 @@
import pytest import pytest
from tests.seeder import Seeder
from tests.utils import UtilActions
@pytest.mark.parametrize("db_error", [True, False]) @pytest.mark.parametrize("db_error", [True, False])
def test_create(client, app, utils, seeder, mocker, db_error): def test_create(client, app, utils: UtilActions, seeder: Seeder, mocker, db_error):
user_id, admin_unit_id = seeder.setup_base() user_id, admin_unit_id = seeder.setup_base()
event_id = seeder.create_event(admin_unit_id) event_id = seeder.create_event(admin_unit_id)
@ -15,7 +18,7 @@ def test_create(client, app, utils, seeder, mocker, db_error):
if db_error: if db_error:
utils.mock_db_commit(mocker) utils.mock_db_commit(mocker)
mail_mock = utils.mock_send_mails(mocker) mail_mock = utils.mock_send_mails_async(mocker)
response = utils.post_form( response = utils.post_form(
url, url,
response, response,
@ -50,7 +53,7 @@ def test_create(client, app, utils, seeder, mocker, db_error):
) )
def test_create_duplicateNotAllowed(client, app, utils, seeder): def test_create_duplicateNotAllowed(client, app, utils: UtilActions, seeder: Seeder):
user_id, admin_unit_id = seeder.setup_base() user_id, admin_unit_id = seeder.setup_base()
event_id = seeder.create_event(admin_unit_id) event_id = seeder.create_event(admin_unit_id)
other_user_id = seeder.create_user("other@test.de") other_user_id = seeder.create_user("other@test.de")
@ -80,7 +83,9 @@ def test_create_duplicateNotAllowed(client, app, utils, seeder):
assert b"duplicate key" in response.data assert b"duplicate key" in response.data
def test_create_unverifiedAdminUnitNotAllowed(client, app, utils, seeder): def test_create_unverifiedAdminUnitNotAllowed(
client, app, utils: UtilActions, seeder: Seeder
):
_, admin_unit_id = seeder.setup_base(admin_unit_verified=False) _, admin_unit_id = seeder.setup_base(admin_unit_verified=False)
event_id = seeder.create_event(admin_unit_id) event_id = seeder.create_event(admin_unit_id)
other_user_id = seeder.create_user("other@test.de") other_user_id = seeder.create_user("other@test.de")
@ -91,14 +96,14 @@ def test_create_unverifiedAdminUnitNotAllowed(client, app, utils, seeder):
utils.assert_response_unauthorized(response) utils.assert_response_unauthorized(response)
def test_create_autoVerify(client, app, utils, seeder, mocker): def test_create_autoVerify(client, app, utils: UtilActions, seeder: Seeder, mocker):
user_id, admin_unit_id = seeder.setup_base() user_id, admin_unit_id = seeder.setup_base()
event_id = seeder.create_event(admin_unit_id) event_id = seeder.create_event(admin_unit_id)
other_user_id = seeder.create_user("other@test.de") other_user_id = seeder.create_user("other@test.de")
other_admin_unit_id = seeder.create_admin_unit(other_user_id, "Other Crew") other_admin_unit_id = seeder.create_admin_unit(other_user_id, "Other Crew")
seeder.create_admin_unit_relation(other_admin_unit_id, admin_unit_id, True) seeder.create_admin_unit_relation(other_admin_unit_id, admin_unit_id, True)
mail_mock = utils.mock_send_mails(mocker) mail_mock = utils.mock_send_mails_async(mocker)
url = utils.get_url("event_reference_request_create", event_id=event_id) url = utils.get_url("event_reference_request_create", event_id=event_id)
response = utils.get_ok(url) response = utils.get_ok(url)
response = utils.post_form( response = utils.post_form(

View File

@ -38,7 +38,7 @@ def test_review_verify(client, seeder, utils, app, mocker, db, db_error, is_veri
if db_error: if db_error:
utils.mock_db_commit(mocker) utils.mock_db_commit(mocker)
mail_mock = utils.mock_send_mails(mocker) mail_mock = utils.mock_send_mails_async(mocker)
response = utils.post_form( response = utils.post_form(
url, url,
response, response,

View File

@ -23,7 +23,7 @@ def test_create(client, app, utils: UtilActions, seeder: Seeder, mocker, db_erro
if db_error: if db_error:
utils.mock_db_commit(mocker) utils.mock_db_commit(mocker)
mail_mock = utils.mock_send_mails(mocker) mail_mock = utils.mock_send_mails_async(mocker)
response = utils.post_form( response = utils.post_form(
url, url,
response, response,

View File

@ -51,7 +51,7 @@ def test_review_verify(
if db_error: if db_error:
utils.mock_db_commit(mocker) utils.mock_db_commit(mocker)
mail_mock = utils.mock_send_mails(mocker) mail_mock = utils.mock_send_mails_async(mocker)
response = utils.post_form( response = utils.post_form(
url, url,
response, response,

View File

@ -197,7 +197,7 @@ def test_event_suggestion_create_for_admin_unit(
if db_error: if db_error:
utils.mock_db_commit(mocker) utils.mock_db_commit(mocker)
mail_mock = utils.mock_send_mails(mocker) mail_mock = utils.mock_send_mails_async(mocker)
if missing_preview_field: if missing_preview_field:
del data["accept_tos"] del data["accept_tos"]