Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,13 @@ CLAUDE.md
requests.http
TODO.txt
.codex

# NodeJS
node_modules/

# Database
db/*.sqlite3
db/db.sqlite3

# Tailwind
src/static/css/tailwind.css
17 changes: 4 additions & 13 deletions src/app/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from django.utils.encoding import iri_to_uri
from django.utils.http import url_has_allowed_host_and_scheme

from app.models import BasicMedia, MediaTypes, Status
from app.models import BasicMedia, MediaTypes


def minutes_to_hhmm(total_minutes):
Expand All @@ -29,7 +29,7 @@ def redirect_back(request):
parsed_url = urlparse(next_url)

# Get the query parameters and remove params we don't want
query_params = dict(parse_qsl(parsed_url.query, keep_blank_values=True))
query_params = dict(parse_qsl(parsed_url.query))
query_params.pop("page", None)
query_params.pop("load_media_type", None)

Expand Down Expand Up @@ -66,7 +66,7 @@ def format_search_response(page, per_page, total_results, results):
}


def enrich_items_with_user_data(request, items, section_name):
def enrich_items_with_user_data(request, items):
"""Enrich a list of items with user tracking data."""
if not items:
return []
Expand Down Expand Up @@ -118,18 +118,9 @@ def enrich_items_with_user_data(request, items, section_name):
else:
key = (str(item["media_id"]), item["source"])

media_item = media_lookup.get(key)
if (
request.user.hide_completed_recommendations
and section_name == "recommendations"
and media_item
and media_item.status == Status.COMPLETED.value
):
continue

enriched_item = {
"item": item,
"media": media_item,
"media": media_lookup.get(key),
}
enriched_items.append(enriched_item)

Expand Down
41 changes: 39 additions & 2 deletions src/app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,16 @@ def get_historical_models(self):
"""Return list of historical model names."""
return [f"historical{media_type}" for media_type in MediaTypes.values]

def get_media_list(self, user, media_type, status_filter, sort_filter, search=None):
def get_media_list(
self,
user,
media_type,
status_filter,
sort_filter,
search=None,
exclude_item_ids=None,
include_item_ids=None,
):
"""Get media list based on filters, sorting and search."""
model = apps.get_model(app_label="app", model_name=media_type)
queryset = model.objects.filter(user=user.id)
Expand All @@ -234,6 +243,12 @@ def get_media_list(self, user, media_type, status_filter, sort_filter, search=No
if search:
queryset = queryset.filter(item__title__icontains=search)

if exclude_item_ids:
queryset = queryset.exclude(item_id__in=exclude_item_ids)

if include_item_ids:
queryset = queryset.filter(item_id__in=include_item_ids)

queryset = queryset.annotate(
repeats=Window(
expression=Count("id"),
Expand Down Expand Up @@ -409,7 +424,15 @@ def _sort_generic_media_list(self, queryset, sort_filter):
models.functions.Lower("item__title"),
)

def get_in_progress(self, user, sort_by, items_limit, specific_media_type=None):
def get_in_progress(
self,
user,
sort_by,
items_limit,
specific_media_type=None,
exclude_item_ids=None,
include_item_ids=None,
):
"""Get a media list of in progress media by type."""
list_by_type = {}
media_types = self._get_media_types_to_process(user, specific_media_type)
Expand All @@ -421,8 +444,22 @@ def get_in_progress(self, user, sort_by, items_limit, specific_media_type=None):
media_type=media_type,
status_filter=Status.IN_PROGRESS.value,
sort_filter=None,
exclude_item_ids=exclude_item_ids,
include_item_ids=include_item_ids,
)

if not media_list:
continue

# Filter out special episodes (Season 0) if disabled
if (
media_type == MediaTypes.SEASON.value
and not user.show_special_episodes
):
media_list = [
m for m in media_list if m.item.season_number != 0
]

if not media_list:
continue

Expand Down
12 changes: 6 additions & 6 deletions src/app/providers/comicvine.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ def comic(media_id):
)

publisher_id = response.get("publisher", {}).get("id")
publisher_comics = []
recommendations = []
if publisher_id:
publisher_comics = get_publisher_comics(publisher_id, media_id)
recommendations = get_similar_comics(publisher_id, media_id)

data = {
"media_id": media_id,
Expand All @@ -154,7 +154,7 @@ def comic(media_id):
"last_updated": response.get("date_last_updated").split()[0],
},
"related": {
"recommendations": publisher_comics,
"from_the_same_publisher": recommendations,
},
# used for events fetching
"last_issue_id": response["last_issue"]["id"],
Expand Down Expand Up @@ -249,9 +249,9 @@ def get_people(response):
return [person["name"] for person in people[:5] if isinstance(person, dict)]


def get_publisher_comics(publisher_id, current_id, limit=15):
"""Get comics from the same publisher."""
cache_key = f"{Sources.COMICVINE.value}_publisher_{publisher_id}_{current_id}"
def get_similar_comics(publisher_id, current_id, limit=10):
"""Get similar comics from the same publisher."""
cache_key = f"{Sources.COMICVINE.value}_similar_{publisher_id}_{current_id}"
data = cache.get(cache_key)

if data is None:
Expand Down
6 changes: 2 additions & 4 deletions src/app/providers/igdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ def search(query, page):
Sources.IGDB.value,
"POST",
url,
data=multiquery,
data=data,
headers=headers,
)

Expand Down Expand Up @@ -282,8 +282,7 @@ def game(media_id):
"expansions.name,expansions.cover.image_id,"
"standalone_expansions.name,standalone_expansions.cover.image_id,"
"expanded_games.name,expanded_games.cover.image_id,"
"similar_games.name,similar_games.cover.image_id,"
"dlcs.name,dlcs.cover.image_id;"
"similar_games.name,similar_games.cover.image_id;"
f"where id = {media_id};"
)
headers = {
Expand Down Expand Up @@ -345,7 +344,6 @@ def game(media_id):
"remasters": get_related(response.get("remasters")),
"remakes": get_related(response.get("remakes")),
"expansions": get_related(response.get("expansions")),
"dlcs": get_related(response.get("dlcs")),
"standalone_expansions": get_related(
response.get("standalone_expansions"),
),
Expand Down
4 changes: 2 additions & 2 deletions src/app/providers/tmdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ def movie(media_id):
"related": {
collection_response.get("name", "collection"): collection_items,
"recommendations": get_related(
filtered_recommendations,
filtered_recommendations[:15],
MediaTypes.MOVIE.value,
),
},
Expand Down Expand Up @@ -445,7 +445,7 @@ def process_tv(response):
response,
),
"recommendations": get_related(
response.get("recommendations", {}).get("results", []),
response.get("recommendations", {}).get("results", [])[:15],
MediaTypes.TV.value,
),
},
Expand Down
15 changes: 0 additions & 15 deletions src/app/templatetags/app_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,18 +433,3 @@ def get_pagination_range(current_page, total_pages, window):
result.append(total_pages)

return result


@register.filter
def show_media_score(rating, user):
"""
Return if we should show the rating of a media.

Args:
rating: the rating value of the media
user: the user to check preferences for

Returns:
True if we should show the media score
"""
return rating is not None and (not user.hide_zero_rating or rating > 0)
59 changes: 1 addition & 58 deletions src/app/tests/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ def test_enrich_items_with_user_data(self):
},
]

enriched_items = enrich_items_with_user_data(self.request, raw_items, "test")
enriched_items = enrich_items_with_user_data(self.request, raw_items)
self.assertEqual(len(enriched_items), 3)

# Scenario 1: Existing movie with user tracking data
Expand Down Expand Up @@ -192,60 +192,3 @@ def test_enrich_items_with_user_data(self):
unknown_movie_enriched["item"]["description"],
"This movie doesn't exist in our database",
)

def test_hide_completed_recommendations_enabled(self):
"""Test that completed items are hidden when preference is enabled."""
self.user.hide_completed_recommendations = True
self.user.save()

raw_items = [
{
"media_id": "238", # This is our completed movie
"source": Sources.TMDB.value,
"media_type": MediaTypes.MOVIE.value,
"title": "Test Movie",
"image": "http://example.com/movie.jpg",
},
{
"media_id": "99999", # Not tracked
"source": Sources.TMDB.value,
"media_type": MediaTypes.MOVIE.value,
"title": "Unknown Movie",
"image": "http://example.com/unknown.jpg",
},
]

# When section is "recommendations", completed items should be hidden
enriched_items = enrich_items_with_user_data(
self.request, raw_items, "recommendations"
)
self.assertEqual(len(enriched_items), 1)
self.assertEqual(enriched_items[0]["item"]["media_id"], "99999")

def test_hide_completed_recommendations_disabled(self):
"""Test that completed items are shown when preference is disabled."""
self.user.hide_completed_recommendations = False
self.user.save()

raw_items = [
{
"media_id": "238", # This is our completed movie
"source": Sources.TMDB.value,
"media_type": MediaTypes.MOVIE.value,
"title": "Test Movie",
"image": "http://example.com/movie.jpg",
},
{
"media_id": "99999",
"source": Sources.TMDB.value,
"media_type": MediaTypes.MOVIE.value,
"title": "Unknown Movie",
"image": "http://example.com/unknown.jpg",
},
]

# With preference disabled, all items should be returned
enriched_items = enrich_items_with_user_data(
self.request, raw_items, "recommendations"
)
self.assertEqual(len(enriched_items), 2)
19 changes: 0 additions & 19 deletions src/app/tests/test_templatetags.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,22 +356,3 @@ def test_icon_media_types(self):
self.assertTrue(len(inactive_result) > 0)
except KeyError:
self.fail(f"icon raised KeyError for {media_type}")

def test_show_media_score(self):
"""Test if we should show media rating or not."""
# Create mock users
mock_user_show = MagicMock()
mock_user_show.hide_zero_rating = False

mock_user_hide = MagicMock()
mock_user_hide.hide_zero_rating = True

# With hide_zero_rating=False, show all non-None scores
self.assertTrue(app_tags.show_media_score(1, mock_user_show))
self.assertTrue(app_tags.show_media_score(0, mock_user_show))
self.assertFalse(app_tags.show_media_score(None, mock_user_show))

# With hide_zero_rating=True, hide zero scores
self.assertTrue(app_tags.show_media_score(1, mock_user_hide))
self.assertFalse(app_tags.show_media_score(0, mock_user_hide))
self.assertFalse(app_tags.show_media_score(None, mock_user_hide))
1 change: 1 addition & 0 deletions src/app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

urlpatterns = [
path("", views.home, name="home"),
path("home/hide/<int:item_id>", views.toggle_home_item, name="toggle_home_item"),
path("medialist/<media_type:media_type>", views.media_list, name="medialist"),
path("search", views.media_search, name="search"),
path(
Expand Down
Loading