Skip to content

Commit

Permalink
Merge pull request #4498 from v-anne/4486-email-pray-and-pay
Browse files Browse the repository at this point in the history
4486 email pray and pay
  • Loading branch information
mlissner authored Sep 30, 2024
2 parents 8d1e916 + 89dd954 commit 1830fb0
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 6 deletions.
78 changes: 78 additions & 0 deletions cl/favorites/templates/prayer_email.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
{% load text_filters %}
{% load humanize %}
{% load tz %}

<!DOCTYPE html>
<html style="font-size: 100.01%; font-weight: inherit; font-family: inherit; border: 0; vertical-align: baseline; font-style: inherit; margin: 0; padding: 0;">
<head>
<meta charset="utf-8">
<style type="text/css">
a:visited { text-decoration: none !important; }
a:hover { text-decoration: none !important; }
a:focus { text-decoration: none !important; }
</style>
</head>
<body style="font-size: 75%; font-weight: inherit; line-height: 1.5; font-family: 'Helvetica Neue', Arial, Helvetica, sans-serif; color: #222; border: 0; vertical-align: baseline; font-style: inherit; background: #fff; margin: 0; padding: 0;">
<h1 class="bottom" style="font-size: 3em; font-weight: normal; line-height: 1; font-family: inherit; color: #111; border: 0; vertical-align: baseline; font-style: inherit; margin: 0; padding: 0;">
<a href="https://www.courtlistener.com" style="font-size: 100%; font-weight: inherit; font-family: inherit; color: #111; border: 0; font-style: inherit; padding: 0; text-decoration: none; vertical-align: baseline; margin: 0;">
Your Wish Has Been Granted!
</a>
</h1>
<hr style="background: #ddd; color: #ddd; clear: both; float: none; width: 60%; height: .1em; margin: 0 0 1.45em; border: none;">

<p>The document you were waiting for is now available in RECAP.</p>
<h2 style="font-size: 2em; font-weight: normal; font-family: inherit; color: #111; border: 0; vertical-align: baseline; font-style: inherit; margin: 0; padding: 0;">
{{ docket|best_case_name|safe }}
{% if docket.docket_number %}({{ docket.docket_number }}){% endif %}
</h2>
<h3 class="alt bottom" style="font-size: 1.5em; font-weight: normal; line-height: 1; font-family: 'Warnock Pro', 'Goudy Old Style','Palatino','Book Antiqua', Georgia, serif; color: #666; border: 0; vertical-align: baseline; margin: 0; padding: 0;">{{ docket.court }}</h3>
<p style="font-weight: inherit; font-family: inherit; border: 0; vertical-align: baseline; font-style: inherit; margin: 0 0 1.5em; padding: 0;">
<a href="https://www.courtlistener.com{{ docket.get_absolute_url }}?order_by=desc">View Docket on CourtListener</a><br>
</p>

<table cellpadding="8">
<thead>
<tr>
<th style="text-align: center">Document<br>Number</th>
<th>Date&nbsp;Filed</th>
<th>Description</th>
<th>View Document</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">{{ docket_entry.entry_number }}{% if rd.attachment_number %}-{{ rd.attachment_number }}{% endif %}</td>
<td>
{% if docket_entry.datetime_filed %}
<span title="{{ docket_entry.datetime_filed|timezone:timezone}}">{{ docket_entry.datetime_filed|timezone:timezone|date:"M j, Y" }}</span>
{% else %}
{{ docket_entry.date_filed|date:"M j, Y"|default:'<em class="gray">Unknown</em>' }}
{% endif %}
</td>
<td>
{% if rd.description %}
{{ rd.description|safe }}
{% else %}
{{ docket_entry.description|safe|default:"<em>Unknown</em>"|safe }}
{% endif %}
</td>
<td>
<a href="https://www.courtlistener.com{{document_url}}">Click here to view.
</a>
</td>
</tr>
</tbody>
</table>

<p>You requested it on {{ date_created|date:"M j, Y" }}
<p>{{num_waiting}} people were also waiting for it.</p>
{% if price %}<p>Somebody paid ${{ price }} to make it available to all of us.</p>{% endif %}

<p style="font-size: 1em; font-weight: inherit; font-family: inherit; border: 0; vertical-align: baseline; font-style: inherit; margin: 0 0 1.5em; padding: 0;">
<strong>This alert brought to you by the non-profit Free Law Project.
<a href="https://donate.free.law/forms/supportflp" style="font-size: 100%; font-weight: inherit; font-family: inherit; color: #009; border: 0; font-style: inherit; padding: 0; text-decoration: none; vertical-align: baseline; margin: 0;">
Please donate to support our work</a>.
</strong>
</p>
</body>
</html>
33 changes: 33 additions & 0 deletions cl/favorites/templates/prayer_email.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{% load text_filters %}{% load humanize %}{% load tz %}*****************
CourtListener.com
*****************
-------------------------------------------------------
Your Wish Has Been Granted!
-------------------------------------------------------

The document you were waiting for is now available in RECAP.
-------------------------------------------------------

{{ docket|best_case_name|safe }} {% if docket.docket_number %}({{ docket.docket_number }}){% endif %}
{{ docket.court }}
~~~
View Docket: https://www.courtlistener.com{{ docket.get_absolute_url }}?order_by=desc

Document Number: {{ docket_entry.entry_number }}{% if rd.attachment_number %}-{{ rd.attachment_number }}{% endif %}
Date Filed: {% if docket_entry.datetime_filed %}{{ docket_entry.datetime_filed|timezone:timezone|date:"M j, Y" }}{% else %}{{ docket_entry.date_filed|date:"M j, Y"|default:'Unknown' }}{% endif %}
Description: {% if rd.description %}{{ rd.description|safe|wordwrap:80 }}{% else %}{{ docket_entry.description|default:"Unknown docket entry description"|safe|wordwrap:80 }}{% endif %}
View Document: https://www.courtlistener.com{{document_url}}
~~~
You requested it on {{ date_created|date:"M j, Y" }}
{{num_waiting}} people were also waiting for it.
Somebody paid ${{ price }} to make it available to all of us.

************************
This alert brought to you by the 501(c)(3) non-profit Free Law Project
- Blog: https://free.law
- BlueSky: https://bsky.app/profile/free.law
- X: https://x.com/freelawproject
- Donate: https://donate.free.law/forms/supportflp
- Become a Member: https://donate.free.law/forms/membership

Please donate to support our work.
64 changes: 62 additions & 2 deletions cl/favorites/tests.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import math
import time
from datetime import timedelta
from datetime import date, timedelta
from http import HTTPStatus

import time_machine
from asgiref.sync import sync_to_async
from django.contrib.auth.hashers import make_password
from django.core import mail
from django.test import AsyncClient, override_settings
from django.urls import reverse
from django.utils.timezone import now
from selenium.webdriver.common.by import By
from timeout_decorator import timeout_decorator

from cl.custom_filters.templatetags.pacer import price
from cl.favorites.factories import NoteFactory, PrayerFactory
from cl.favorites.models import DocketTag, Note, Prayer, UserTag
from cl.favorites.utils import create_prayer, get_top_prayers, prayer_eligible
Expand Down Expand Up @@ -848,15 +850,19 @@ async def test_prayers_integration(self) -> None:
"""Integration test for prayers."""

rd_6 = await sync_to_async(RECAPDocumentFactory)(
docket_entry__entry_number=6,
docket_entry__date_filed=date(2015, 8, 16),
pacer_doc_id="98763427",
document_number="1",
is_available=False,
page_count=10,
description="Dismissing Case",
)

current_time = now()
with time_machine.travel(current_time, tick=False):
# Create prayers
await create_prayer(self.user, rd_6)
prayer_1 = await create_prayer(self.user, rd_6)
await create_prayer(self.user_2, rd_6)
await create_prayer(self.user, self.rd_4)

Expand Down Expand Up @@ -897,6 +903,60 @@ async def test_prayers_integration(self) -> None:
msg="Wrong number of granted prayers",
)

# Assert that prayer granted email notifications are properly sent to users.
self.assertEqual(
len(mail.outbox), 2, msg="Wrong number of emails sent."
)
self.assertIn(
"A document you requested is now on CourtListener",
mail.outbox[0].subject,
)

email_text_content = mail.outbox[0].body
html_content = None
for content, content_type in mail.outbox[0].alternatives:
if content_type == "text/html":
html_content = content
break

self.assertIn(
f"https://www.courtlistener.com{rd_6.get_absolute_url()}",
email_text_content,
)
self.assertIn(
f"You requested it on {prayer_1.date_created.strftime("%b %d, %Y")}",
email_text_content,
)
self.assertIn(
f"{len(actual_top_prayers)} people were also waiting for it.",
email_text_content,
)
self.assertIn(
f"Somebody paid ${price(rd_6)}",
email_text_content,
)

self.assertIn(
f"https://www.courtlistener.com{rd_6.get_absolute_url()}",
html_content,
)
self.assertIn(
f"{len(actual_top_prayers)} people were also waiting for it.",
html_content,
)
self.assertIn(
f"You requested it on {prayer_1.date_created.strftime("%b %d, %Y")}",
html_content,
)
self.assertIn(
f"Somebody paid ${price(rd_6)}",
html_content,
)
email_recipients = {email.to[0] for email in mail.outbox}
self.assertEqual(
email_recipients, {self.user_2.email, self.user.email}
)

top_prayers = await get_top_prayers()
self.assertEqual(len(top_prayers), 1, msg="Wrong top_prayers.")
self.assertEqual(
Expand Down
55 changes: 55 additions & 0 deletions cl/favorites/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from django.conf import settings
from django.contrib.auth.models import User
from django.core.mail import EmailMultiAlternatives, get_connection
from django.db.models import (
Avg,
Count,
Expand All @@ -12,8 +13,10 @@
Subquery,
)
from django.db.models.functions import Cast, Extract, Now, Sqrt
from django.template import loader
from django.utils import timezone

from cl.custom_filters.templatetags.pacer import price
from cl.favorites.models import Prayer
from cl.search.models import RECAPDocument

Expand Down Expand Up @@ -93,3 +96,55 @@ async def get_top_prayers() -> list[RECAPDocument]:
.order_by("-geometric_mean")[:50]
)
return [doc async for doc in documents.aiterator()]


def send_prayer_emails(instance: RECAPDocument) -> None:
open_prayers = Prayer.objects.filter(
recap_document=instance, status=Prayer.WAITING
).select_related("user")
# Retrieve email recipients before updating granted prayers.
email_recipients = [
{
"email": prayer["user__email"],
"date_created": prayer["date_created"],
}
for prayer in open_prayers.values("user__email", "date_created")
]
open_prayers.update(status=Prayer.GRANTED)

# Send email notifications in bulk.
if email_recipients:
subject = f"A document you requested is now on CourtListener"
txt_template = loader.get_template("prayer_email.txt")
html_template = loader.get_template("prayer_email.html")

docket = instance.docket_entry.docket
docket_entry = instance.docket_entry
document_url = instance.get_absolute_url()
num_waiting = len(email_recipients)
doc_price = price(instance)

messages = []
for email_recipient in email_recipients:
context = {
"docket": docket,
"docket_entry": docket_entry,
"rd": instance,
"document_url": document_url,
"num_waiting": num_waiting,
"price": doc_price,
"date_created": email_recipient["date_created"],
}
txt = txt_template.render(context)
html = html_template.render(context)
msg = EmailMultiAlternatives(
subject=subject,
body=txt,
from_email=settings.DEFAULT_ALERTS_EMAIL,
to=[email_recipient["email"]],
headers={"X-Entity-Ref-ID": f"prayer.rd.pk:{instance.pk}"},
)
msg.attach_alternative(html, "text/html")
messages.append(msg)
connection = get_connection()
connection.send_messages(messages)
6 changes: 2 additions & 4 deletions cl/search/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from cl.citations.tasks import (
find_citations_and_parantheticals_for_recap_documents,
)
from cl.favorites.models import Prayer
from cl.favorites.utils import send_prayer_emails
from cl.lib.es_signal_processor import ESSignalProcessor
from cl.people_db.models import (
ABARating,
Expand Down Expand Up @@ -574,6 +574,4 @@ def handle_recap_doc_change(
instance.es_rd_field_tracker.has_changed("is_available")
and instance.is_available == True
):
Prayer.objects.filter(
recap_document=instance, status=Prayer.WAITING
).update(status=Prayer.GRANTED)
send_prayer_emails(instance)

0 comments on commit 1830fb0

Please sign in to comment.