-
-
Notifications
You must be signed in to change notification settings - Fork 151
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
base: main
Are you sure you want to change the base?
Changes from 37 commits
7411262
46c4f42
5eff9fa
2ff2881
e940feb
062e2df
7ea9ec9
09de777
f8a1afe
6742acd
8ab1c09
f7a782e
6bd3449
f8d3027
e1da82f
70f8022
d9e1b7f
678f5cc
b63b27b
4eb6484
053ea35
f3f7cf6
bc1ffa0
7d5482c
dc30884
3070c1a
6e6fa50
4fc041d
903e8c9
9f3dc28
fcb76fc
f8468b0
6abc9bd
7969cbd
2622117
b6341e2
62c994b
4d4716a
e5e6974
9aa8653
e2bf8cd
8e21511
53abe40
6e61a2f
0d06a15
373d8ab
c0f8ca7
950d914
d75b384
feb14dc
7b6f50f
88094d3
d6cc9a7
8f2fd2f
7ef948d
5721192
daa13c6
7ed3380
439a99c
37732e3
df8e1b2
7ae6196
029061a
c6e5d01
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 %} |
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, | ||||||
|
@@ -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( | ||||||
|
@@ -147,6 +154,46 @@ async def get_top_prayers() -> list[RECAPDocument]: | |||||
return [doc async for doc in documents.aiterator()] | ||||||
|
||||||
|
||||||
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()] | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
|
||||||
def send_prayer_emails(instance: RECAPDocument) -> None: | ||||||
open_prayers = Prayer.objects.filter( | ||||||
recap_document=instance, status=Prayer.WAITING | ||||||
|
@@ -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) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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 |
There was a problem hiding this comment.
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