Skip to content

Add the option to filter lists by status#928

Merged
FuzzyGrim merged 4 commits intoFuzzyGrim:devfrom
doluk:lists-filter-by-status
Jan 14, 2026
Merged

Add the option to filter lists by status#928
FuzzyGrim merged 4 commits intoFuzzyGrim:devfrom
doluk:lists-filter-by-status

Conversation

@doluk
Copy link
Contributor

@doluk doluk commented Oct 24, 2025

Thanks for the amazing project. I really enjoy to use it. In the lack of genre labels (like sci-fi for example covering multiple media types) I created a sci-fi list and just added my items to it. But then I run into the issue, that I would only want to see planned items on my sci-fi list....

This pr aims to add filtering by status to the users list similar to how the medialist has it as an option. This is my first time working with Django, so any feedback is appreciated and please excuse any beginner mistakes I made.

I added also a test for it. While working on the tests, I moved the duplicated Movie, etc creation into the setUp function. I just noticed, that I accidentally commited my change to the source of the test items. While someone could argue, that is better to test without the metadata api requests, I am happy to revert the change (from a clean branch, so it isn't in the commit history).

Copilot summary

This pull request introduces a new feature that allows users to filter items in a custom list by their status (e.g., Completed, In Progress, Planning, etc.) in the list detail view. The changes span backend logic, frontend UI, and database schema to support status-based filtering and user preference persistence.

Key changes include:

Backend: Status Filtering and Persistence

  • Added a new list_detail_status field to the User model and database, with choices for all relevant statuses and a migration to enforce data integrity. This allows each user to persist their preferred status filter for list detail views. [1] [2] [3]
  • Updated the list_detail view in lists/views.py to accept a status parameter, apply filtering by status across all media types, and pass the current status and available choices to the template context. [1] [2] [3] [4]

Frontend: UI for Status Filtering

  • Enhanced the list detail template (list_detail.html) to add a status filter dropdown alongside the media type filter, update the Alpine.js state, and ensure the status filter is included in form submissions and HTMX requests. [1] [2] [3]

Testing: Test Refactoring and Coverage

  • Refactored tests in test_views.py to create media instances in setUp instead of each test, and added/updated tests to verify status filtering logic and UI. [1] [2] [3] [4] [5] [6]

These changes together provide a robust, user-friendly way to filter list items by status, and ensure the feature is well-tested and persists user preferences.


Backend: Status Filtering and Persistence

  • Added a list_detail_status field to the User model with choices and a migration, along with a database constraint to ensure valid values. [1] [2] [3]
  • Updated the list_detail view to filter items by status, persist the user's choice, and pass status-related context to the template. [1] [2] [3] [4]

Frontend: UI for Status Filtering

  • Added a status filter dropdown in list_detail.html, updated Alpine.js state, and ensured filter state is included in form and HTMX requests. [1] [2] [3]

Testing: Test Refactoring and Coverage

  • Refactored test setup to create media instances once per test case, and updated/added tests to cover status filtering and UI changes. [1] [2] [3] [4] [5] [6]

doluk added 3 commits October 24, 2025 09:37
- Introduced `list_detail_status` in user preferences with validations and choices.
- Updated templates to include UI for status filtering.
- Enhanced backend to filter items based on the selected status.
- Applied database constraints for valid `list_detail_status` values.
…iciency

- Removed redundant default status assignment.
- Consolidated media filtering logic to avoid duplication.
- Ensured distinct media type retrieval for better query optimization.
- Streamlined media object mapping to improve code maintainability.
@tbmedia
Copy link

tbmedia commented Jan 5, 2026

This is a huge, HUGE feature for most people and I feel like it's being overshadowed by other requests. Allow me to quickly explain my use case, and (other than the high fees now), why I'm leaving Trakt:

I have a custom list of things me and my partner love watching. We filter this list by "unwatched", and boom! All the new things every week pop right up.

Additionally, it would be nice to set a custom order for shows. Bob's Burger's is one of our favorites, it would be so cool to see that as #1. Then, #2 (if there's a new episode) might be something like Hazbin Hotel, another favorite.

I don't see any way to do this on Yamtrack... which is a bummer, because I absolutely love this software. Trakt removed this from their app recently, which was a huge step backwards. If it can be added here, that would be so darn helpful, practical, and COOL. Please consider, and thank you so much for sharing this wonderful software with us!!

Edit: I'm actually going to likely pay Trakt more money at this point purely because I can't seem to find this feature here, or on SIMKL (unless I pay them almost as much as Trakt wants). If there's a way to put a bounty on certain features, I will absolutely fork over cash for a feature like this!

Copilot AI review requested due to automatic review settings January 14, 2026 21:43
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request adds status-based filtering functionality to custom list detail views, allowing users to filter list items by their completion status (e.g., Completed, In Progress, Planning, etc.). The implementation includes backend changes to persist user preferences, frontend UI enhancements with a new filter dropdown, and comprehensive test coverage.

Changes:

  • Added a list_detail_status field to the User model to persist status filter preferences across sessions
  • Implemented status filtering logic in the list_detail view to filter items based on user's status across different media types
  • Enhanced the list detail template with a new status filter dropdown UI using Alpine.js and HTMX

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/users/models.py Added list_detail_status field with MediaStatusChoices and database constraint
src/users/migrations/0038_user_list_detail_status_alter_user_home_sort_and_more.py Migration to add the new field and constraints
src/lists/views.py Implemented status filtering logic and updated context to include status choices
src/templates/lists/list_detail.html Added status filter dropdown UI and integrated with Alpine.js state management
src/lists/tests/test_views.py Refactored test setup and added test coverage for status filtering

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +125 to +163
# Get distinct media types for filtering by status
media_types = items.values_list("media_type", flat=True).distinct()
media_by_item_id = {}
media_manager = MediaManager()

# Filter by status if specified
if params["status_filter"] != MediaStatusChoices.ALL:
# Get the list of item IDs that have the specified status for the current user
# We need to check across all media types since items can be of different types
item_ids_with_status = set()

for media_type in media_types:
model = apps.get_model("app", media_type)

if media_type == MediaTypes.EPISODE.value:
# Episodes are linked through seasons
filter_kwargs = {
"related_season__user": request.user,
"related_season__status": params["status_filter"],
"item__in": [item.id for item in items],
}
else:
filter_kwargs = {
"user": request.user,
"status": params["status_filter"],
"item__in": [item.id for item in items],
}
queryset = model.objects.filter(**filter_kwargs).select_related("item")
queryset = media_manager._apply_prefetch_related(queryset, media_type)
media_manager.annotate_max_progress(queryset, media_type)

# Map media objects by item_id
for entry in queryset:
media_by_item_id.setdefault(entry.item_id, entry)
item_ids_with_status.add(entry.item_id)

# Filter items to only those with the specified status
items = items.filter(id__in=item_ids_with_status)

Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

When status_filter is not set to ALL, the media objects are fetched and stored in media_by_item_id during the status filtering logic. However, when status_filter is ALL, no media objects are fetched, which means items_page items won't have their media attribute set. This creates inconsistent behavior where items have media objects only when filtering by status but not when showing all items. The media fetching logic that was removed should still execute for items that weren't already processed during status filtering.

Copilot uses AI. Check for mistakes.
filter_kwargs = {
"related_season__user": request.user,
"related_season__status": params["status_filter"],
"item__in": [item.id for item in items],
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

This implementation creates an N+1 query pattern. The code iterates over all items in the queryset with [item.id for item in items], which forces evaluation of the queryset. This happens before any actual filtering, pagination, or processing occurs. For large lists, this could be inefficient. Consider using items.values_list('id', flat=True) instead to avoid loading full Item objects when you only need IDs.

Copilot uses AI. Check for mistakes.
Comment on lines +144 to +150
"item__in": [item.id for item in items],
}
else:
filter_kwargs = {
"user": request.user,
"status": params["status_filter"],
"item__in": [item.id for item in items],
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

This implementation creates an N+1 query pattern. The code iterates over all items in the queryset with [item.id for item in items], which forces evaluation of the queryset. This happens before any actual filtering, pagination, or processing occurs. For large lists, this could be inefficient. Consider using items.values_list('id', flat=True) instead to avoid loading full Item objects when you only need IDs.

Suggested change
"item__in": [item.id for item in items],
}
else:
filter_kwargs = {
"user": request.user,
"status": params["status_filter"],
"item__in": [item.id for item in items],
"item__in": items.values_list("id", flat=True),
}
else:
filter_kwargs = {
"user": request.user,
"status": params["status_filter"],
"item__in": items.values_list("id", flat=True),

Copilot uses AI. Check for mistakes.
Comment on lines +320 to +322
"""Test the list_detail view with status filter."""
mock_update_preference.return_value = "date_added"
mock_user_can_view.return_value = True
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

The mock for update_preference only returns "date_added", but the view calls update_preference twice: once for "list_detail_sort" and once for "list_detail_status". The mock should either use side_effect to return different values for each call, or the test should verify that both calls are being made correctly. Currently, the status filter would get "date_added" as its value which is invalid for MediaStatusChoices, but the update_preference implementation should handle this by returning the current value. This test might not be catching potential bugs in the update_preference logic.

Copilot uses AI. Check for mistakes.
mock_update_preference.return_value = "date_added"
mock_user_can_view.return_value = True

# Test the view with media type filter
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

The comment says "Test the view with media type filter" but this test is actually testing status filtering, not media type filtering. The comment should be updated to accurately reflect what the test does.

Suggested change
# Test the view with media type filter
# Test the view with status filter

Copilot uses AI. Check for mistakes.
</div>

<div x-data="{ sort: '{{ current_sort }}', type: '{{ request.GET.type|default:'all' }}', query: '{{ request.GET.q|default:''|escapejs }}', mediaTypeLabels: {
<div x-data="{ sort: '{{ current_sort }}', type: '{{ request.GET.type|default:'all' }}', status: '{{ current_status }}', query: '{{ request.GET.q|default:'' }}', mediaTypeLabels: {
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

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

The escapejs filter was removed from the query parameter initialization in the Alpine.js data object. This could lead to JavaScript errors or potential XSS vulnerabilities if the query parameter contains quotes or other special characters. The escapejs filter should be restored to properly escape JavaScript strings.

Suggested change
<div x-data="{ sort: '{{ current_sort }}', type: '{{ request.GET.type|default:'all' }}', status: '{{ current_status }}', query: '{{ request.GET.q|default:'' }}', mediaTypeLabels: {
<div x-data="{ sort: '{{ current_sort|escapejs }}', type: '{{ request.GET.type|default:'all'|escapejs }}', status: '{{ current_status|escapejs }}', query: '{{ request.GET.q|default:''|escapejs }}', mediaTypeLabels: {

Copilot uses AI. Check for mistakes.
@FuzzyGrim
Copy link
Owner

Hey, thanks for the PR! I appreciate your work on this.

GitHub Copilot auto-review got triggered somehow (not sure why that happened), and it flagged a few minor issues. The suggestions look valid, but since this PR has been open for a while and the core functionality is solid, I'm going to merge it now and handle those fixes in a follow-up commit.

Thanks again for the contribution!

@FuzzyGrim FuzzyGrim merged commit 0506f1a into FuzzyGrim:dev Jan 14, 2026
7 checks passed
@FuzzyGrim
Copy link
Owner

@tbmedia could you create a new issue for that feature request?

badboy added a commit to badboy/Yamtrack that referenced this pull request Jan 15, 2026
Without it this currently results in an error at startup on a fresh
checkout, e.g. when just doing `docker compose up`:

    yamtrack        | CommandError: Conflicting migrations detected; multiple leaf nodes in the migration graph: (0038_user_list_detail_status_alter_user_home_sort_and_more, 0039_user_clickable_media_cards in users).
    yamtrack        | To fix them run 'python manage.py makemigrations --merge'
    yamtrack exited with code 1 (restarting)

`0038_user_list_detail_status_alter_user_home_sort_and_more` was added in FuzzyGrim#928,
but by the time it was merged another migration had already been added as well.
@FuzzyGrim
Copy link
Owner

Added in v0.25.0.

alexlebens pushed a commit to alexlebens/infrastructure that referenced this pull request Feb 7, 2026
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [FuzzyGrim/Yamtrack](https://github.com/FuzzyGrim/Yamtrack) | minor | `0.24.11` → `0.25.0` |
| [ghcr.io/fuzzygrim/yamtrack](https://github.com/FuzzyGrim/Yamtrack) | minor | `0.24.11` → `0.25.0` |

---

### Release Notes

<details>
<summary>FuzzyGrim/Yamtrack (FuzzyGrim/Yamtrack)</summary>

### [`v0.25.0`](https://github.com/FuzzyGrim/Yamtrack/releases/tag/v0.25.0)

[Compare Source](FuzzyGrim/Yamtrack@v0.24.11...v0.25.0)

##### Features

- Added support for the official Jellyfin Webhook plugin [@&#8203;Oridjinn1980](https://github.com/Oridjinn1980) in [#&#8203;907](FuzzyGrim/Yamtrack#907)
- Added the option to filter lists by status by [@&#8203;doluk](https://github.com/doluk) in [#&#8203;928](FuzzyGrim/Yamtrack#928)
- Added external links (imdb, tvdb, wikidata) to media details page [#&#8203;937](FuzzyGrim/Yamtrack#937) ([`38673ca`](FuzzyGrim/Yamtrack@38673ca))
- Added new option to select default date when bulk completing tv shows/seasons [#&#8203;802](FuzzyGrim/Yamtrack#802) ([`d2df3cd`](FuzzyGrim/Yamtrack@d2df3cd))
- Added user-customizable date and time display formats across the application [#&#8203;624](FuzzyGrim/Yamtrack#624) ([`070cfc8`](FuzzyGrim/Yamtrack@070cfc8))
- Added Board Game tracking with BoardGameGeek integration by [@&#8203;zskemp](https://github.com/zskemp) in [#&#8203;979](FuzzyGrim/Yamtrack#979)
- Allow importing TV seasons/episodes by title only from Yamtrack CSV format by [@&#8203;dpantel](https://github.com/dpantel) in [#&#8203;968](FuzzyGrim/Yamtrack#968)
- Display movie collections on media detail page by [@&#8203;andrebk](https://github.com/andrebk) in [#&#8203;1003](FuzzyGrim/Yamtrack#1003)
- Added `CELERY_REDIS_URL` to allow configuring celery redis url independently from django [#&#8203;1123](FuzzyGrim/Yamtrack#1123) ([`ed20461`](FuzzyGrim/Yamtrack@ed20461))
- Added progress bar on media cards by [@&#8203;busliggabor](https://github.com/busliggabor) in [#&#8203;1130](FuzzyGrim/Yamtrack#1130)

##### Fixes

- Fixed comic events not showing issue number ([`9f71132`](FuzzyGrim/Yamtrack@9f71132))
- Fixed some log entries getting incorrectly labeled as error log [#&#8203;1056](FuzzyGrim/Yamtrack#1056) ([`a47bf3d`](FuzzyGrim/Yamtrack@a47bf3d))
- Fixed season episodes notifications not being sent when tv is enabled but season disabled [#&#8203;1057](FuzzyGrim/Yamtrack#1057) ([`9947cbe`](FuzzyGrim/Yamtrack@9947cbe))
- Fixed docker secrets file parsing [#&#8203;789](FuzzyGrim/Yamtrack#789) ([`495de72`](FuzzyGrim/Yamtrack@495de72))
- Fixed rating style by [@&#8203;busliggabor](https://github.com/busliggabor) in [#&#8203;1086](FuzzyGrim/Yamtrack#1086)
- Improved release dates metadata for Hardcover books [#&#8203;966](FuzzyGrim/Yamtrack#966) [`bb083ef`](FuzzyGrim/Yamtrack@bb083ef)
- Fixed end date gets auto filled to current datetime when setting progress to maximum [#&#8203;1091](FuzzyGrim/Yamtrack#1091) ([`9765be7`](FuzzyGrim/Yamtrack@9765be7))
- Fixed wrong total anime episodes when AniList episode data is wrong compared to MyAnimeList [#&#8203;1096](FuzzyGrim/Yamtrack#1096) ([`c43d712`](FuzzyGrim/Yamtrack@c43d712))
- Fixed incorrect upcoming episode time on Home Page depending on time [#&#8203;1100](FuzzyGrim/Yamtrack#1100) ([`974d711`](FuzzyGrim/Yamtrack@974d711))
- Fixed can't create users with admin page [#&#8203;1147](FuzzyGrim/Yamtrack#1147) ([`11d9649`](FuzzyGrim/Yamtrack@11d9649))

##### Maintenance

- build(deps-dev): bump coverage from 7.13.0 to 7.13.1 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;1064](FuzzyGrim/Yamtrack#1064)
- build(deps): bump django-widget-tweaks from 1.5.0 to 1.5.1 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;1075](FuzzyGrim/Yamtrack#1075)
- build(deps): bump aiohttp from 3.13.2 to 3.13.3 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;1076](FuzzyGrim/Yamtrack#1076)
- build(deps): bump celery from 5.6.0 to 5.6.2 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;1077](FuzzyGrim/Yamtrack#1077)
- build(deps): bump pillow from 12.0.0 to 12.1.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;1078](FuzzyGrim/Yamtrack#1078)
- build(deps): bump requests-ratelimiter from 0.7.0 to 0.8.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;1079](FuzzyGrim/Yamtrack#1079)
- build(deps): bump django-select2 from 8.4.7 to 8.4.8 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;1085](FuzzyGrim/Yamtrack#1085)
- build(deps-dev): bump ruff from 0.14.10 to 0.14.13 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;1107](FuzzyGrim/Yamtrack#1107)
- build(deps): bump django-allauth\[socialaccount] from 65.13.1 to 65.14.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;1116](FuzzyGrim/Yamtrack#1116)
- build(deps): bump django from 5.2.9 to 5.2.11 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;1153](FuzzyGrim/Yamtrack#1153)
- build(deps-dev): bump fakeredis from 2.32.1 to 2.33.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;1156](FuzzyGrim/Yamtrack#1156)
- build(deps): bump apprise from 1.9.6 to 1.9.7 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;1157](FuzzyGrim/Yamtrack#1157)
- build(deps-dev): bump coverage from 7.13.1 to 7.13.3 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;1159](FuzzyGrim/Yamtrack#1159)
- build(deps): bump gunicorn from 23.0.0 to 25.0.1 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;1160](FuzzyGrim/Yamtrack#1160)
- build(deps): bump django-debug-toolbar from 6.1.0 to 6.2.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;1161](FuzzyGrim/Yamtrack#1161)
- build(deps): bump django-health-check from 3.20.8 to 3.23.3 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;1162](FuzzyGrim/Yamtrack#1162)

##### New Contributors

- [@&#8203;Oridjinn1980](https://github.com/Oridjinn1980) made their first contribution in [#&#8203;907](FuzzyGrim/Yamtrack#907)
- [@&#8203;doluk](https://github.com/doluk) made their first contribution in [#&#8203;928](FuzzyGrim/Yamtrack#928)
- [@&#8203;zskemp](https://github.com/zskemp) made their first contribution in [#&#8203;979](FuzzyGrim/Yamtrack#979)
- [@&#8203;dpantel](https://github.com/dpantel) made their first contribution in [#&#8203;968](FuzzyGrim/Yamtrack#968)

**Full Changelog**: <FuzzyGrim/Yamtrack@v0.24.11...v0.25.0>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4zLjYiLCJ1cGRhdGVkSW5WZXIiOiI0My4zLjYiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbImltYWdlIl19-->

Reviewed-on: https://gitea.alexlebens.dev/alexlebens/infrastructure/pulls/3812
Co-authored-by: Renovate Bot <renovate-bot@alexlebens.net>
Co-committed-by: Renovate Bot <renovate-bot@alexlebens.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants