Skip to content

Commit

Permalink
First flight launched notification
Browse files Browse the repository at this point in the history
Creates a front draft when the first flight is launched for an
advertiser. Also renames the user field for flight notifications to be
more obvious.

I do wonder if we should just have this notification for *all* flights
instead of just the first one
  • Loading branch information
davidfischer committed Aug 16, 2024
1 parent 871ac5d commit 0bade90
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 12 deletions.
9 changes: 8 additions & 1 deletion adserver/auth/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@ class UserAdmin(SimpleHistoryAdmin):
(None, {"fields": ("email", "name", "password")}),
(
_("Ad server details"),
{"fields": ("advertisers", "publishers", "notify_on_completed_flights")},
{
"fields": (
"advertisers",
"publishers",
"flight_notifications",
"notify_on_completed_flights", # DEPRECATED
)
},
),
(
_("Permissions"),
Expand Down
27 changes: 27 additions & 0 deletions adserver/auth/migrations/0007_rename_flight_notifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 5.0.7 on 2024-08-16 22:00

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("adserver_auth", "0006_simple_history_upgrade"),
]

operations = [
migrations.AddField(
model_name="historicaluser",
name="flight_notifications",
field=models.BooleanField(
default=True, help_text="Receive email notification about ad flights"
),
),
migrations.AddField(
model_name="user",
name="flight_notifications",
field=models.BooleanField(
default=True, help_text="Receive email notification about ad flights"
),
),
]
23 changes: 23 additions & 0 deletions adserver/auth/migrations/0008_data_flight_notifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 5.0.7 on 2024-08-16 22:01

from django.db import migrations


def forwards(apps, schema_editor):
"""Update flight notifications field."""
User = apps.get_model("adserver_auth", "User")

for user in User.objects.all():
user.flight_notifications = user.notify_on_completed_flights
user.save()


class Migration(migrations.Migration):

dependencies = [
("adserver_auth", "0007_rename_flight_notifications"),
]

operations = [
migrations.RunPython(forwards, reverse_code=migrations.RunPython.noop)
]
5 changes: 5 additions & 0 deletions adserver/auth/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ class User(AbstractBaseUser, PermissionsMixin):
publishers = models.ManyToManyField(Publisher, blank=True)

# Notifications
flight_notifications = models.BooleanField(
default=True,
help_text=_("Receive email notification about ad flights"),
)
# DEPRECATED and replaced by `flight_notifications`
notify_on_completed_flights = models.BooleanField(
default=True,
help_text=_(
Expand Down
8 changes: 4 additions & 4 deletions adserver/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -1357,8 +1357,8 @@ def save(self, commit=True):
user = super().save(commit)
user.invite_user()

# Track who added this user
update_change_reason(user, "Invited via authorized users view")
# Track who added this user
update_change_reason(user, "Invited via authorized users view")

# You will need to add the user to the publisher/advertiser in the view
return user
Expand Down Expand Up @@ -1387,15 +1387,15 @@ def __init__(self, *args, **kwargs):
),
Fieldset(
_("Notification settings"),
"notify_on_completed_flights",
"flight_notifications",
css_class="my-3",
),
Submit("submit", _("Update account")),
)

class Meta:
model = get_user_model()
fields = ("name", "notify_on_completed_flights")
fields = ("name", "flight_notifications")


class SupportForm(forms.Form):
Expand Down
63 changes: 57 additions & 6 deletions adserver/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,61 @@ def notify_on_ad_image_change(advertisement_id):
)


@app.task()
def notify_of_first_flight_launched():
"""Notify when an advertiser's first ever flight launches."""
start_date = get_ad_day().date() - datetime.timedelta(days=1)
site = get_current_site(request=None)

# Get advertisers who launched today and
# exclude advertisers with flights launched before today
advertisers_launched_today = Flight.objects.filter(
live=True,
start_date=start_date,
).values("campaign__advertiser")
advertisers_launched_before_today = Flight.objects.filter(
start_date__lt=start_date,
).values("campaign__advertiser")

for advertiser in Advertiser.objects.filter(
pk__in=advertisers_launched_today
).exclude(pk__in=advertisers_launched_before_today):
log.debug(
"Advertiser with first flights launched today. advertiser=%s", advertiser
)

flights = Flight.objects.filter(
live=True,
start_date=start_date,
campaign__advertiser=advertiser,
).select_related()

if settings.FRONT_ENABLED:
to_addresses = [
u.email for u in advertiser.user_set.all() if u.flight_notifications
]

context = {
"site": site,
"flights": flights,
"advertiser": advertiser,
}

with mail.get_connection(
settings.FRONT_BACKEND,
sender_name=f"{site.name} Flight Tracker",
) as connection:
message = mail.EmailMessage(
_("Advertising launched - %(name)s") % {"name": site.name},
render_to_string("adserver/email/flights-launched.html", context),
from_email=settings.DEFAULT_FROM_EMAIL, # Front doesn't use this
to=to_addresses,
connection=connection,
)
message.draft = True # Only create a draft for now
message.send()


@app.task()
def notify_of_autorenewing_flights(days_before=7):
"""Send a note to flights set to renew automatically."""
Expand All @@ -781,9 +836,7 @@ def notify_of_autorenewing_flights(days_before=7):
site = get_current_site(request=None)

to_addresses = [
u.email
for u in advertiser.user_set.all()
if u.notify_on_completed_flights
u.email for u in advertiser.user_set.all() if u.flight_notifications
]

context = {
Expand Down Expand Up @@ -932,9 +985,7 @@ def notify_of_completed_flights():
advertiser = Advertiser.objects.get(slug=advertiser_slug)

to_addresses = [
u.email
for u in advertiser.user_set.all()
if u.notify_on_completed_flights
u.email for u in advertiser.user_set.all() if u.flight_notifications
]

if not to_addresses:
Expand Down
2 changes: 1 addition & 1 deletion adserver/templates/adserver/email/advertiser-base.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
{% url "account" as notification_settings_url %}
<p>
<small>{% blocktrans with site_name=site.name %}You are receiving this email because you run advertising with {{ site_name }}.{% endblocktrans %} </small>
<small>{% blocktrans with site_domain=site.domain %}Adjust your <a href="{{ site_domain }}{{ notification_settings_url }}?ref=email-renewal">notification settings</a>{% endblocktrans %} in our dashboard.</small>
<small>{% blocktrans with site_domain=site.domain %}Adjust your <a href="{{ site_domain }}{{ notification_settings_url }}?ref=email-notifications">notification settings</a>{% endblocktrans %} in our dashboard.</small>
</p>
{% endblock body %}
30 changes: 30 additions & 0 deletions adserver/templates/adserver/email/flights-launched.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{% extends 'adserver/email/advertiser-base.html' %}
{% load i18n %}


{% block content %}
<p>{% blocktrans with advertiser_name=advertiser.name %}{{ advertiser_name }} team,{% endblocktrans %}</p>

<p>{% blocktrans with site_name=site.name %}Congrats on launching your first ad flight with {{ site_name }}!{% endblocktrans %}</p>

{% spaceless %}

<p>{% blocktrans with total_flights=flights|length pluralized_flights=flights|length|pluralize %}
You have {{ total_flights }} flight{{ pluralized_flights }} that launched today.
Below are links to your flight{{ pluralized_flights }} and performance reports in our ad dashboard:
{% endblocktrans %}</p>
<ul>
{% for flight in flights %}
{% url "flight_report" advertiser.slug flight.slug as flight_report_url %}
<li>
<a href="{{ site.domain }}{{ flight.get_absolute_url }}?ref=email-flight-launched">{{ flight.name }}</a>
<span> (<a href="{{ site.domain }}{{ flight_report_url }}?ref=email-flight-launched">{% trans 'report' %}</a>)</span>
</li>
{% endfor %}
</ul>

<p>{% blocktrans %}Thanks for advertising with us and don't hesitate to let us know if there's anything we can do to help make advertising with us a success for you.{% endblocktrans %}</p>

{% endspaceless %}

{% endblock content %}
26 changes: 26 additions & 0 deletions adserver/tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from ..tasks import disable_inactive_publishers
from ..tasks import notify_of_autorenewing_flights
from ..tasks import notify_of_completed_flights
from ..tasks import notify_of_first_flight_launched
from ..tasks import notify_of_publisher_changes
from ..tasks import remove_old_client_ids
from ..tasks import remove_old_report_data
Expand Down Expand Up @@ -138,6 +139,31 @@ def test_calculate_ad_ctrs(self):
self.assertAlmostEqual(self.ad1.sampled_ctr, 100 * (1 / 10))
self.assertAlmostEqual(self.ad2.sampled_ctr, 100 * (2 / 7))

@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_notify_of_flights_launched(self):
# Ensure there's a recipient for a wrapup email
self.staff_user.advertisers.add(self.advertiser)

notify_of_first_flight_launched()

# Shouldn't be any flight launched messages
self.assertEqual(len(mail.outbox), 0)

self.flight.start_date = get_ad_day().date() - datetime.timedelta(days=1)
self.flight.save()

notify_of_first_flight_launched()

# Should be one email for the flight that launched 'yesterday' now
self.assertEqual(len(mail.outbox), 1)
self.assertTrue(
mail.outbox[0].subject.startswith("Advertising launched")
)

@override_settings(
# Use the memory email backend instead of front for testing
FRONT_BACKEND="django.core.mail.backends.locmem.EmailBackend",
Expand Down

0 comments on commit 0bade90

Please sign in to comment.