Skip to content

Commit

Permalink
Merge pull request #755 from readthedocs/davidfischer/rework-payout-e…
Browse files Browse the repository at this point in the history
…mail-view-form

Rework the payout email view/form
  • Loading branch information
davidfischer committed Jun 14, 2023
2 parents dc990fa + 7bbc55c commit 7b0aa5b
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 60 deletions.
54 changes: 27 additions & 27 deletions adserver/staff/forms.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Views for the administrator actions."""
import logging

import requests
import stripe
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Field
Expand All @@ -12,6 +11,7 @@
from django import forms
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core import mail
from django.utils import timezone
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
Expand Down Expand Up @@ -363,39 +363,39 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.add_input(Submit("submit", "Send email"))
self.helper.attrs = {"id": "payout-start"}
self.fields["body"].widget.attrs["data-bind"] = "textInput: body"

def _send_email(self):
token = getattr(settings, "FRONT_TOKEN")
channel = getattr(settings, "FRONT_CHANNEL")

headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}

payload = {
"to": [user.email for user in self.publisher.user_set.all()],
"sender_name": self.cleaned_data["sender"],
"subject": self.cleaned_data["subject"],
"options": {"archive": self.cleaned_data["archive"]},
"body": self.cleaned_data["body"],
}

url = f"https://api2.frontapp.com/channels/{channel}/messages"
if self.cleaned_data["draft"]:
url = f"https://api2.frontapp.com/channels/{channel}/drafts"
# Allow the team to see the draft
payload["mode"] = "shared"
# Author is required on drafts..
payload["author_id"] = getattr(settings, "FRONT_AUTHOR")

log.debug(
"Sending email draft=%s archive=%s",
self.cleaned_data["draft"],
self.cleaned_data["archive"],
)
response = requests.request("POST", url, json=payload, headers=headers)
log.debug("Response: %s", response.status_code)

backend = None # Use the default settings.EMAIL_BACKEND
if settings.FRONT_ENABLED:
backend = settings.FRONT_BACKEND

with mail.get_connection(
backend,
sender_name=self.cleaned_data["sender"],
) as connection:
message = mail.EmailMessage(
self.cleaned_data["subject"],
self.cleaned_data["body"],
from_email=settings.DEFAULT_FROM_EMAIL, # Front doesn't use this
to=[user.email for user in self.publisher.user_set.all()],
connection=connection,
)

if self.cleaned_data["draft"]:
message.draft = True

if self.cleaned_data["archive"]:
message.archive = True

message.send()

def save(self):
"""Do the work to save the payout."""
Expand Down
2 changes: 1 addition & 1 deletion adserver/staff/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ def get_initial(self):
get_template("adserver/email/publisher-payout.html")
.render(self.data)
.replace("\n\n", "\n")
)
).strip()
initial["sender"] = "EthicalAds by Read the Docs"
initial["subject"] = f"EthicalAds Payout - {self.publisher.name}"
initial["body"] = email_html
Expand Down
43 changes: 19 additions & 24 deletions adserver/templates/adserver/email/publisher-payout.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,26 @@
</p>

<p>
<strong>Stay up to date:</strong>
If you want to keep up to date with the latest features and updates from EthicalAds,
we recommend reading our <a href="https://www.ethicalads.io/blog/">blog</a> or <a href="https://twitter.com/ethicaladsio">Twitter</a>.
If you prefer getting updates in your inbox,
you can subscribe to our <a href="https://ethicalads.us17.list-manage.com/subscribe/post?u=ca5e74de3ea2867d373058271&id=5746f18bb8">mailing list</a> as well.
</p>

<p>
Thanks for being one of our publishers on the EthicalAds network.
We do payouts by the 15th of the month,
as noted in our <a href="https://www.ethicalads.io/publisher-policy/">Publisher Policy</a>.
If you haven't had a chance to look it over, please do,
as it sets expectations around ad placements, payments, and more.
We are now processing payments for <strong>{{ today|date:"F" }} {{ today|date:"Y" }}</strong>,
and you made a total of <strong>${{ due_report.total.revenue_share|floatformat:2 }}</strong> for ads displayed between {{ start_date|date:"F j" }}-{{ end_date|date:"F j" }}.
You can find the full report for this billing cycle on our <a href="{{ due_report_url }}">revenue report</a>.
</p>

{% if due_report.total.ctr < .07 %} {# Only warn is a good deal below .1 #}
<p>
We generally expect all our publishers to maintain a CTR (click though rate) around or above .1%.
We generally expect all our publishers to maintain a CTR (click though rate) around or above 0.1%.
Your CTR is currently <strong>{{ due_report.total.ctr|floatformat:3 }}%</strong>,
which is below our current minimum.
We have a few suggestions in our <a href="https://www.ethicalads.io/publisher-guide/">Publisher Guide</a> around improving placements,
but the main thing is just having the ad be on the screen in a visible place for long enough for users to see and click on it.
</p>
{% endif %}

<p>
We are now processing payments for <strong>{{ today|date:"F" }} {{ today|date:"Y" }}</strong>,
and you made a total of <strong>${{ due_report.total.revenue_share|floatformat:2 }}</strong> for ads displayed between <strong>{{ start_date|date:"F j" }}-{{ end_date|date:"F j" }}</strong>.
You can find the full report for this billing cycle on our <a href="{{ due_report_url }}">revenue report</a>.
</p>

{% if first %}
<p>
We need a few pieces of information from you in order to process a payment:
<strong>Information required!</strong>
We need a few pieces of information from you in order to process a payment:
</p>

<p>
Expand All @@ -55,10 +41,10 @@
</p>

<p>
<strong>Please reply to this email with the name & address of the person or organization receiving this payment.</strong>
Please reply to this email with the name & address of the person or organization receiving this payment.
Once we have this information, we will process the payment.
These will show up in the <a href="{{ payouts_url }}">payouts dashboard</a>,
once they have been started.
once they have been processed.
</p>
{% else %}
<p>
Expand All @@ -69,12 +55,21 @@
</p>
{% endif %}

<p>
<strong>Stay up to date:</strong>
If you want to keep up to date with the latest features and updates from EthicalAds,
we recommend reading our <a href="https://www.ethicalads.io/blog/">blog</a> or <a href="https://twitter.com/ethicaladsio">Twitter</a>.
If you prefer getting updates in your inbox,
you can subscribe to our <a href="https://ethicalads.us17.list-manage.com/subscribe/post?u=ca5e74de3ea2867d373058271&id=5746f18bb8">mailing list</a> as well.
</p>

<p>
Thanks again for being part of the EthicalAds network.
If you are enjoying EthicalAds,
we'd love to ask you to recommend us to a friend.
We are looking to grow our publisher network,
and are always on the look out for other developer-focused publishers.
If there's a feature or change that would improve EthicalAds
or would have made your onboarding or payout process better,
please respond to this email and let us know!
</p>

<p>
Expand Down
6 changes: 6 additions & 0 deletions adserver/templates/adserver/staff/publisher-payout-start.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ <h1>{% block heading %}{% trans 'Start Payout' %}{% endblock heading %}</h1>
<div class="col-md">
{% crispy form form.helper %}
</div>

<div class="col-md-6 ml-md-5">
<h5>{% trans 'Preview' %}</h5>

<div class="border bg-light p-2" data-bind="html: displayBody()"></div>
</div>
</div>

{% endblock content_container %}
20 changes: 12 additions & 8 deletions adserver/tests/test_staff_actions.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from datetime import timedelta
from unittest.mock import patch

from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.core import mail
from django.test import override_settings
from django.test import TestCase
from django.urls import reverse
Expand Down Expand Up @@ -306,11 +306,17 @@ def test_list_view_filters(self):
self.assertContains(list_response, "<td>$70.00</td>")
self.assertContains(list_response, f"{self.publisher1.name}</a></td>")

@override_settings(FRONT_TOKEN="test", FRONT_CHANNEL="test", FRONT_AUTHOR="test")
@patch("adserver.staff.forms.requests.request")
def test_create_view(self, mock_request):
@override_settings(
# Use the memory email backend instead of front for testing
FRONT_BACKEND="django.core.mail.backends.locmem.EmailBackend",
FRONT_ENABLED=True,
)
def test_create_view(self):
self.client.force_login(self.staff_user)

# Need a user otherwise the email isn't sent because there's no recipients
self.user.publishers.add(self.publisher1)

# Start payout
start_url = reverse(
"staff-start-publisher-payout",
Expand All @@ -334,10 +340,8 @@ def test_create_view(self, mock_request):
post_response = self.client.post(start_url, data=data)
self.assertEqual(post_response.status_code, 302)

self.assertEqual(
mock_request.call_args[0],
("POST", "https://api2.frontapp.com/channels/test/messages"),
)
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, "Test subject")

def test_finish_view(self):
self.client.force_login(self.staff_user)
Expand Down
1 change: 1 addition & 0 deletions assets/src/views/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './publisher_settings';
export * from './advertisement-form';
export * from './flight_update';
export * from './dashboard-home';
export * from './payout-create';
15 changes: 15 additions & 0 deletions assets/src/views/payout-create.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const ko = require('knockout');


function PayoutStartViewModel() {
this.body = ko.observable(document.querySelector("#id_body").value);

this.displayBody = function () {
return this.body;
};
}


if (document.querySelectorAll("#payout-start").length > 0) {
ko.applyBindings(new PayoutStartViewModel());
}

0 comments on commit 7b0aa5b

Please sign in to comment.