Skip to content

Commit ed8efb9

Browse files
committed
feat: enhance media progress tracking and display upcoming episodes
1 parent fdb7de1 commit ed8efb9

File tree

5 files changed

+225
-54
lines changed

5 files changed

+225
-54
lines changed

src/app/database.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33

44
from django.apps import apps
55
from django.db import models
6-
from django.db.models import Count, F, Max
6+
from django.db.models import Count, F, Max, Min, Q
77
from django.db.models.functions import TruncDate
8+
from django.utils import timezone
89

910
from app.models import Item, Media
1011

@@ -70,6 +71,7 @@ def get_historical_models():
7071

7172
def get_in_progress(user):
7273
"""Get a media list of in progress media by type."""
74+
today = timezone.now().date()
7375
list_by_type = {}
7476

7577
for media_type in Item.MediaTypes.values:
@@ -84,9 +86,17 @@ def get_in_progress(user):
8486
],
8587
sort_filter="score",
8688
)
87-
if media_list:
89+
if media_list.exists():
8890
media_list = media_list.annotate(
8991
max_progress=Max("item__event__episode_number"),
92+
next_episode_number=Min(
93+
"item__event__episode_number",
94+
filter=Q(item__event__date__gt=today),
95+
),
96+
next_episode_date=Min(
97+
"item__event__date",
98+
filter=Q(item__event__date__gt=today),
99+
),
90100
)
91101
list_by_type[media_type] = media_list
92102

src/app/templatetags/app_tags.py

Lines changed: 74 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from django import template
44
from django.urls import reverse
5+
from django.utils import timezone
56
from django.utils.html import format_html
67
from unidecode import unidecode
78

@@ -91,10 +92,45 @@ def media_color(media_type):
9192
return models.Item.Colors[media_type.upper()].value
9293

9394

95+
@register.filter
96+
def percentage_ratio(value, total):
97+
"""Calculate percentage, showing one decimal place for values between 0 and 1."""
98+
try:
99+
if total == 0:
100+
return "0"
101+
102+
result = (Decimal(value) / Decimal(total)) * 100
103+
104+
# If result is between 0 and 1, show one decimal
105+
if 0 < result < 1:
106+
return f"{result:.1f}"
107+
108+
# For all other values, show as integer
109+
return str(int(round(result)))
110+
except (TypeError, ValueError):
111+
return "0"
112+
113+
114+
@register.filter
115+
def naturalday(value):
116+
"""Return the natural day for the date."""
117+
today = timezone.now().date()
118+
diff = value - today
119+
days = diff.days
120+
days_threshold = 5
121+
122+
if days == 0:
123+
return "Today"
124+
if days == 1:
125+
return "Tomorrow"
126+
if days > 1 and days <= days_threshold:
127+
return f"In {days} days"
128+
return value.strftime("%b %d, %Y")
129+
130+
94131
@register.filter
95132
def media_url(media):
96133
"""Return the media URL for both metadata and model object cases."""
97-
# Check if media is metadata or model instance
98134
is_dict = isinstance(media, dict)
99135

100136
# Get attributes using either dict access or object attribute
@@ -126,52 +162,55 @@ def media_url(media):
126162
)
127163

128164

129-
@register.filter
130-
def percentage_ratio(value, total):
131-
"""Calculate percentage, showing one decimal place for values between 0 and 1."""
132-
try:
133-
if total == 0:
134-
return "0"
135-
136-
result = (Decimal(value) / Decimal(total)) * 100
137-
138-
# If result is between 0 and 1, show one decimal
139-
if 0 < result < 1:
140-
return f"{result:.1f}"
141-
142-
# For all other values, show as integer
143-
return str(int(round(result)))
144-
except (TypeError, ValueError):
145-
return "0"
146-
147-
148165
@register.simple_tag
149166
def component_id(component_type, media):
150-
"""Return the component ID."""
151-
component_id = f"{component_type}-{media['media_type']}-{media['media_id']}"
167+
"""Return the component ID for both metadata and model object cases."""
168+
is_dict = isinstance(media, dict)
169+
170+
# Get base attributes using either dict access or object attribute
171+
media_type = media["media_type"] if is_dict else media.media_type
172+
media_id = media["media_id"] if is_dict else media.media_id
173+
174+
component_id = f"{component_type}-{media_type}-{media_id}"
152175

153-
if "season_number" in media:
154-
component_id += f"-{media['season_number']}"
155-
if "episode_number" in media:
156-
component_id += f"-{media['episode_number']}"
176+
# Handle season/episode numbers if they exist
177+
if is_dict:
178+
if "season_number" in media:
179+
component_id += f"-{media['season_number']}"
180+
if "episode_number" in media:
181+
component_id += f"-{media['episode_number']}"
182+
else:
183+
if media.season_number is not None:
184+
component_id += f"-{media.season_number}"
185+
if media.episode_number is not None:
186+
component_id += f"-{media.episode_number}"
157187

158188
return component_id
159189

160190

161191
@register.simple_tag
162192
def modal_url(modal_type, media):
163-
"""Return the modal URL."""
193+
"""Return the modal URL for both metadata and model object cases."""
194+
is_dict = isinstance(media, dict)
195+
196+
# Build kwargs using either dict access or object attribute
164197
kwargs = {
165-
"source": media["source"],
166-
"media_type": media["media_type"],
167-
"media_id": media["media_id"],
198+
"source": media["source"] if is_dict else media.source,
199+
"media_type": media["media_type"] if is_dict else media.media_type,
200+
"media_id": media["media_id"] if is_dict else media.media_id,
168201
}
169202

170-
if "season_number" in media:
171-
kwargs["season_number"] = media["season_number"]
172-
173-
if "episode_number" in media:
174-
kwargs["episode_number"] = media["episode_number"]
203+
# Handle season/episode numbers if they exist
204+
if is_dict:
205+
if "season_number" in media:
206+
kwargs["season_number"] = media["season_number"]
207+
if "episode_number" in media:
208+
kwargs["episode_number"] = media["episode_number"]
209+
else:
210+
if media.season_number is not None:
211+
kwargs["season_number"] = media.season_number
212+
if media.episode_number is not None:
213+
kwargs["episode_number"] = media.episode_number
175214

176215
return reverse(f"{modal_type}_modal", kwargs=kwargs)
177216

src/events/tasks.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import logging
2-
from datetime import datetime
2+
from datetime import date, datetime
33
from zoneinfo import ZoneInfo
44

55
import requests
@@ -79,9 +79,7 @@ def process_item(item, events_bulk):
7979
except requests.exceptions.HTTPError as err:
8080
# happens for niche media in which the mappings during import are incorrect
8181
if err.response.status_code == requests.codes.not_found:
82-
msg = f"{item} ({item.media_id}) not found on {item.source}. Deleting it."
83-
logger.warning(msg)
84-
item.delete()
82+
pass
8583
else:
8684
raise
8785

@@ -207,17 +205,18 @@ def process_other(item, metadata, events_bulk):
207205
episode_number = 1
208206
else:
209207
episode_number = None
210-
211208
events_bulk.append(
212209
Event(item=item, episode_number=episode_number, date=air_date),
213210
)
214211
except ValueError:
215212
pass
216213
if item.media_type == "manga" and metadata["max_progress"]:
214+
# MyAnimeList manga has an end date when it's completed
217215
if "end_date" in metadata["details"] and metadata["details"]["end_date"]:
218216
air_date = date_parser(metadata["details"]["end_date"])
217+
# MangaUpdates doesn't have an end date, so use a placeholder
219218
else:
220-
air_date = date_parser("9999-12-31")
219+
air_date = date.min
221220
events_bulk.append(
222221
Event(
223222
item=item,

0 commit comments

Comments
 (0)