Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(favorites): frontend revisions to pray and pay project #4580

Open
wants to merge 64 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
7411262
adding prayer button to RECAP Document page.
v-anne Oct 16, 2024
46c4f42
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 16, 2024
5eff9fa
fixing link
v-anne Oct 16, 2024
2ff2881
Merge branch 'main' into 4507-pray-and-pay-API
v-anne Oct 16, 2024
e940feb
feat(prayers): Enhances top prayers template
ERosendo Oct 16, 2024
062e2df
feat(favorites): user prayer page
v-anne Oct 16, 2024
7ea9ec9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 16, 2024
09de777
modifying format based on tags page
v-anne Oct 16, 2024
f8a1afe
changes to user prayer page
v-anne Oct 16, 2024
6742acd
minor html
v-anne Oct 16, 2024
8ab1c09
more html - adding date_created to user wishlist page
v-anne Oct 16, 2024
f7a782e
adjusting button placement
v-anne Oct 16, 2024
6bd3449
adding prayer button to redirect modal
v-anne Oct 16, 2024
f8d3027
changes to user_prayers.html
v-anne Oct 17, 2024
e1da82f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 17, 2024
70f8022
restricting access for now to only the given user
v-anne Oct 17, 2024
d9e1b7f
restricting access
v-anne Oct 17, 2024
678f5cc
Merge branch '4507-pray-and-pay-API' of https://github.com/v-anne/cou…
v-anne Oct 17, 2024
b63b27b
delete button
v-anne Oct 17, 2024
4eb6484
prayer help page
v-anne Oct 17, 2024
053ea35
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 17, 2024
f3f7cf6
Merge branch 'main' into 4507-pray-and-pay-API
v-anne Oct 17, 2024
bc1ffa0
Merge branch 'main' into 4507-pray-and-pay-API
v-anne Oct 18, 2024
7d5482c
showing users if they can currently make requests
v-anne Oct 18, 2024
dc30884
Merge branch 'freelawproject:main' into 4507-pray-and-pay-API
v-anne Oct 18, 2024
3070c1a
link to user wishlist
v-anne Oct 18, 2024
6e6fa50
Merge branch 'main' into 4507-pray-and-pay-API
v-anne Oct 19, 2024
4fc041d
download button for granted documents
v-anne Oct 19, 2024
903e8c9
modifying function to get filepath of available documents
v-anne Oct 19, 2024
9f3dc28
Merge branch 'main' into 4507-pray-and-pay-API
v-anne Oct 19, 2024
fcb76fc
work on views for help page
v-anne Oct 19, 2024
f8468b0
finished help documentation
v-anne Oct 19, 2024
6abc9bd
minor formatting changes and displaying statistics
v-anne Oct 20, 2024
7969cbd
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 20, 2024
2622117
Merge branch 'main' into 4507-pray-and-pay-API
v-anne Oct 20, 2024
b6341e2
Merge branch 'main' into 4507-pray-and-pay-API
ERosendo Oct 21, 2024
62c994b
feat(prayers): Refine queries to retrieve documents
ERosendo Oct 21, 2024
4d4716a
making suggested changes
v-anne Oct 21, 2024
e5e6974
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 21, 2024
9aa8653
adjusting db query to make it more efficient
v-anne Oct 21, 2024
e2bf8cd
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 21, 2024
8e21511
Merge branch 'main' into 4507-pray-and-pay-API
v-anne Oct 21, 2024
53abe40
changing function to use dataclass
v-anne Oct 21, 2024
6e61a2f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 21, 2024
0d06a15
minor formatting changes and fixing display of summary statistics on …
v-anne Oct 21, 2024
373d8ab
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Oct 21, 2024
c0f8ca7
reordering imports
v-anne Oct 21, 2024
950d914
fixing formatting issues
v-anne Oct 21, 2024
d75b384
Merge branch 'main' into 4507-pray-and-pay-API
v-anne Oct 22, 2024
feb14dc
Merge branch 'main' into 4507-pray-and-pay-API
v-anne Oct 22, 2024
7b6f50f
correcting file name
v-anne Oct 24, 2024
88094d3
fixing js script name
v-anne Oct 24, 2024
d6cc9a7
Merge branch 'main' into 4507-pray-and-pay-API
v-anne Oct 24, 2024
8f2fd2f
reverting change to return statements to avoid tests failing
v-anne Oct 24, 2024
7ef948d
Merge branch '4507-pray-and-pay-API' of https://github.com/v-anne/cou…
v-anne Oct 24, 2024
5721192
Merge branch 'main' into 4507-pray-and-pay-API
v-anne Oct 25, 2024
daa13c6
correcting CL API variable name to work with clone_from_cl.py
v-anne Oct 26, 2024
7ed3380
style(favorites): minor formatting changes in pray-and-pay emails
v-anne Oct 28, 2024
439a99c
fixing test
v-anne Oct 28, 2024
37732e3
fixing plurals in email template
v-anne Oct 29, 2024
df8e1b2
Merge branch 'main' into 4507-pray-and-pay-API
ERosendo Nov 1, 2024
7ae6196
temporarily commenting out images
v-anne Nov 1, 2024
029061a
Merge branch '4507-pray-and-pay-API' of https://github.com/v-anne/cou…
v-anne Nov 1, 2024
c6e5d01
Merge branch 'main' into 4507-pray-and-pay-API
v-anne Nov 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cl/assets/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ <h1>You did not supply the "private" variable to your template.
</li>
{% flag "pray-and-pay" %}
<li>
<a href="{% url "top_prayers" %}#recap-alerts" tabindex="203">Pray and Pay Project</a>
<a href="{% url "top_prayers" %}" tabindex="203">Pray and Pay Project</a>
</li>
{% endflag %}
<li>
Expand Down
42 changes: 34 additions & 8 deletions cl/favorites/templates/top_prayers.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,54 @@


{% block content %}
<div class="col-xs-12">
<h1 class="text-center">Community's Most Requested Documents</h1>
</div>


<div class="col-xs-12">
<div class="well well-sm">
<p>There have been <b>{{ granted_stats.count }}</b> requests granted for <b>{{ granted_stats.num_distinct }}</b> unique documents that cost <b>${{ granted_stats.total_cost|floatformat:2 }}</b>.</p>
<br>
<p>There are <b>{{ waiting_stats.count }}</b> requests pending for <b>{{ waiting_stats.num_distinct }}</b> unique documents that cost at least <b>${{ waiting_stats.total_cost|floatformat:2 }}</b>.</p>
<br>
<p>{% if user.is_authenticated %}
<a href="{% url 'user_prayers' user.username %}">View your prayers</a>
{% else %}
<a href="{% url 'sign-in' %}?next={% url 'top_prayers' %}">Sign in to view your prayers</a>
{% endif %}</p>
</div>
</div>

<div class="col-xs-12">
<div class="table-responsive">
<table class="settings-table table">
<thead>
<tr>
<th>User Preference</th>
<th>Document Description</th>
<th>Court</th>
<th>Case Name</th>
<th>Document Number</th>
<th>PACER Doc ID</th>
<th>Document Court</th>
<th>Document Description</th>
<th>Buy on Pacer</th>
</tr>
</thead>
<tbody>
{% for prayer in top_prayers %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ prayer.docket_entry.docket.court.citation_string }}</td>
{% with docket=prayer.docket_entry.docket %}
<td>
<a href="{% url "view_docket" docket.pk docket.slug %}">
{{ prayer.docket_entry.docket|best_case_name|safe|v_wrapper }} ({{ prayer.docket_entry.docket.docket_number }})
</a>
</td>
<td>
<a href="{% url "view_docket" docket.pk docket.slug %}#entry-{{ prayer.docket_entry.entry_number }}">
{{ prayer.document_number }}
</a>
</td>
{% endwith %}
<td>{{ prayer.description }}</td>
<td>{{ prayer.document_number }}</td>
<td>{{ prayer.pacer_doc_id }}</td>
<td>{{ prayer.docket_entry.docket.court_id }}</td>
<td><a href="{{ prayer.pacer_url }}"
{% if not request.COOKIES.buy_on_pacer_modal and not request.COOKIES.recap_install_plea %}
class="open_buy_pacer_modal btn btn-default btn-xs"
Expand Down
117 changes: 117 additions & 0 deletions cl/favorites/templates/user_prayers.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
{% extends "base.html" %}
{% load extras %}
{% load text_filters %}
{% load static %}
{% load pacer %}
{% load tz %}

{% block title %}{% if is_page_owner %}Your RECAP Requests {% else %}RECAP Requests for: {{ requested_user }}{% endif %} – CourtListener.com{% endblock %}
{% block og_title %}{% if is_page_owner %}Your RECAP Requests {% else %}RECAP Requests for: {{ requested_user }}{% endif %} – CourtListener.com{% endblock %}
{% block description %}CourtListener lets you request purchase of legal documents. View the document requests for {{ requested_user }}.{% endblock %}
{% block og_description %}CourtListener lets you request purchase of legal documents. View the document requests for {{ requested_user }}.{% endblock %}

{% block content %}
<div class="col-xs-12">
<h1 class="text-center">{% if is_page_owner %}Your RECAP Requests {% else %}RECAP Requests for: {{ requested_user }}{% endif %}</h1>
</div>

<div class="col-xs-12">
<div class="well well-sm">
{% if is_page_owner %}
<p>You have had <b>{{ count }}</b> requests fulfilled for documents that cost <b>${{ total_cost|floatformat:2 }}</b>.<p>
<p>{% if is_eligible %}You are eligible to make document requests.{% else %}You have reached your daily limit; wait 24 hours to make new requests.{% endif %}</p>
{% endif %}
</div>
</div>

<div class="col-xs-12">
<div class="table-responsive">
<table class="settings-table table">
<thead>
<tr>
<th>Court</th>
<th>Case Name</th>
<th>Document Number</th>
<th>Document Description</th>
<th>Requested On</th>
<th>Status</th>
{% if is_page_owner %}<th>Delete</th>{% endif %}
</tr>
</thead>
<tbody>
{% for prayer in prayers %}
<tr>
<td>{{ prayer.docket_entry.docket.court.citation_string }}</td>
{% with docket=prayer.docket_entry.docket %}
<td>
<a href="{% url "view_docket" docket.pk docket.slug %}">
{{ prayer.docket_entry.docket|best_case_name|safe|v_wrapper }} ({{ prayer.docket_entry.docket.docket_number }})
</a>
</td>
<td>
<a href="{% url "view_docket" docket.pk docket.slug %}#entry-{{ prayer.docket_entry.entry_number }}">
{{ prayer.document_number }}
</a>
</td>
{% endwith %}
<td>{{ prayer.description }}</td>
<td>{{ prayer.date_created|date:"M j, Y" }}</td>
<td>{% if prayer.status == WAITING %} Pending {% elif prayer.status == GRANTED %}
<div class="btn-group hidden-xs col-sm-4 col-md-3 hidden-print flex">
{% if rd.filepath_local %}
<a href="{{ rd.filepath_local.url }}"
rel="nofollow"
role="button"
class="btn btn-primary btn-xs"
{% if rd.date_upload %}
title="Uploaded {{ rd.date_upload|timezone:timezone }}"
{% endif %}>
Download PDF
</a>
<button type="button"
class="btn btn-primary btn-xs dropdown-toggle"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li>
<a href="{{ rd.filepath_local.url }}" rel="nofollow">From
CourtListener</a>
</li>
{% if rd.filepath_ia %}
<li>
<a href="{{ rd.filepath_ia }}"
rel="nofollow">From
Internet Archive</a>
</li>
{% endif %} {% endif %} {% endif %}</td>
{% if is_page_owner %}<td><a href="{% url 'delete_prayer' recap_document=prayer.pk %}">Delete</a></td>{% endif %}
{% comment %} <td><a href="{{ prayer.pacer_url }}"
{% if not request.COOKIES.buy_on_pacer_modal and not request.COOKIES.recap_install_plea %}
class="open_buy_pacer_modal btn btn-default btn-xs"
data-toggle="modal" data-target="#modal-buy-pacer"
{% else%}
class="btn btn-default btn-xs"
{% endif %}
target="_blank"
rel="nofollow">Buy on PACER {% if prayer.page_count %}(${{ prayer|price }}){% endif %}</td> {% endcomment %}
</tr>
{% empty %}
<tr>
<td colspan="2">No document requests made. Consider making one!</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}

{% block footer-scripts %}
<script defer type="text/javascript"
src="{% static "js/buy_pacer_modal.js" %}"></script>
{% include "includes/buy_pacer_modal.html" %}
{% endblock %}
3 changes: 3 additions & 0 deletions cl/favorites/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
delete_prayer_view,
open_prayers,
save_or_update_note,
user_prayers_view,
view_tag,
view_tags,
)
Expand All @@ -25,6 +26,7 @@
name="view_tag",
),
path("tags/<str:username>/", view_tags, name="tag_list"),
# Prayer pages
path("prayers/top/", open_prayers, name="top_prayers"),
path(
"prayer/create/<int:recap_document>/",
Expand All @@ -36,4 +38,5 @@
delete_prayer_view,
name="delete_prayer",
),
path("prayers/<str:username>/", user_prayers_view, name="user_prayers"),
]
86 changes: 80 additions & 6 deletions cl/favorites/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from datetime import timedelta

from asgiref.sync import sync_to_async
from django.conf import settings
from django.contrib.auth.models import User
from django.core.cache import cache
from django.core.mail import EmailMultiAlternatives, get_connection
from django.db.models import (
Avg,
Expand Down Expand Up @@ -117,11 +119,16 @@ async def get_top_prayers() -> list[RECAPDocument]:
"attachment_number",
"pacer_doc_id",
"page_count",
"is_free_on_pacer",
"description",
"docket_entry__entry_number",
"docket_entry__docket_id",
"docket_entry__docket__slug",
"docket_entry__docket__case_name",
"docket_entry__docket__docket_number",
"docket_entry__docket__pacer_case_id",
"docket_entry__docket__court__jurisdiction",
"docket_entry__docket__court__citation_string",
"docket_entry__docket__court_id",
)
.annotate(
Expand All @@ -147,6 +154,46 @@ async def get_top_prayers() -> list[RECAPDocument]:
return [doc async for doc in documents.aiterator()]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no need to use list comprehensions here. Returning documents directly is fine

- return [doc async for doc in documents.aiterator()]
+ return documents



async def get_user_prayers(user: User) -> list[Prayer]:
user_prayers = Prayer.objects.filter(user=user).values("recap_document_id")

documents = (
RECAPDocument.objects.filter(id__in=Subquery(user_prayers))
.select_related(
"docket_entry",
"docket_entry__docket",
"docket_entry__docket__court",
)
.only(
"pk",
"document_type",
"document_number",
"attachment_number",
"pacer_doc_id",
"page_count",
"is_free_on_pacer",
"description",
"date_created",
"docket_entry__entry_number",
"docket_entry__docket_id",
"docket_entry__docket__slug",
"docket_entry__docket__case_name",
"docket_entry__docket__docket_number",
"docket_entry__docket__pacer_case_id",
"docket_entry__docket__court__jurisdiction",
"docket_entry__docket__court__citation_string",
"docket_entry__docket__court_id",
)
.annotate(
prayer_status=F("prayers__status"),
prayer_date_created=F("prayers__date_created"),
)
.order_by("prayers__date_created")
)

return [document async for document in documents.aiterator()]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return [document async for document in documents.aiterator()]
return documents.all()



def send_prayer_emails(instance: RECAPDocument) -> None:
open_prayers = Prayer.objects.filter(
recap_document=instance, status=Prayer.WAITING
Expand Down Expand Up @@ -211,19 +258,46 @@ async def get_user_prayer_history(user: User) -> tuple[int, float]:
return count, total_cost


async def get_lifetime_prayer_stats() -> tuple[int, int, float]:
async def get_lifetime_prayer_stats(
status: int,
) -> tuple[
int, int, float
]: # status can be only 1 (WAITING) or 2 (GRANTED) based on the Prayer model
v-anne marked this conversation as resolved.
Show resolved Hide resolved

filtered_list = Prayer.objects.filter(status=Prayer.GRANTED)
cache_key = f"prayer-stats-{status}"

data = await cache.aget(cache_key)

if data is not None:
return (
data["count"],
data["num_distinct_purchases"],
data["total_cost"],
)

filtered_list = Prayer.objects.filter(status=status)

count = await filtered_list.acount()

total_cost = 0
distinct_documents = set()

async for prayer in filtered_list:
distinct_documents.add(prayer.recap_document)
total_cost += float(await price(prayer.recap_document))
recap_document = await sync_to_async(lambda: prayer.recap_document)()

num_distinct_purchases = len(distinct_documents)
distinct_documents.add(recap_document)

document_price = price(recap_document)
total_cost += float(document_price) if document_price else 0.0

num_distinct = len(distinct_documents)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should find a better way to count documents and compute the the total price, this approach isn't quite efficient. To optimize performance, we should avoid unnecessary database queries within the loop. Django's select_related and prefetch_related methods can help with this. Additionally, to prevent redundant calculations, we should filter out duplicate results before computing the price.

To address the previously mentioned issues, I suggest we use the following queries:

prayer_by_status = Prayer.objects.filter(status=status)

prayer_count = await prayer_by_status.acount()

distinct_prayers = await prayer_by_status.values("recap_document").distinct().acount()

total_cost = await (
    prayer_by_status.select_related("recap_document")
    .values("recap_document")
    .distinct()
    .annotate(
        price=Case(
            When(recap_document__is_free_on_pacer=True, then=Value(0.0)),
            When(recap_document__page_count__gt=30, then=Value(3.0)),
            When(
                recap_document__page_count__gt=0,
                recap_document__page_count__lte=30,
                then=F("recap_document__page_count") * 0.10,
            ),
            default=Value(0.0),
        )
    )
    .aaggregate(Sum("price", default=0.0))
)


data = {
"count": count,
"num_distinct_purchases": num_distinct,
"total_cost": total_cost,
}
one_day = 60 * 60 * 24
await cache.aset(cache_key, data, one_day)

return count, num_distinct_purchases, total_cost
return count, num_distinct, total_cost
Loading