Add hidden propert to PackageCategory model#1190
Add hidden propert to PackageCategory model#1190MythicManiac wants to merge 2 commits intomasterfrom
Conversation
Add a new `hidden` property to the `PackageCategory` model and disable rendering of hidden categories on the UI side. Include the property in APIs that return categories as objects so frontend can be updated to exclude them.
WalkthroughThis change introduces a hidden boolean field to PackageCategory across backend, schema import, and frontend types. A migration adds the field (default False). The model gains a custom queryset/manager with visible() (hidden=False). Serializers (cyberstorm and experimental) now expose hidden. Views and templates switch category retrieval from .all() to .visible() for package lists, package details, and management UI. Admin adds hidden to list_display and list_filter. Schema import supports hidden in the schema and persists it on sync. Frontend builder types include hidden and filter it out in CommunitySelector. Tests updated/added accordingly. Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1190 +/- ##
==========================================
+ Coverage 92.81% 92.84% +0.02%
==========================================
Files 337 337
Lines 10358 10367 +9
Branches 938 938
==========================================
+ Hits 9614 9625 +11
+ Misses 617 615 -2
Partials 127 127 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
builder/src/api/models.ts(1 hunks)builder/src/components/CommunitySelector.tsx(1 hunks)django/thunderstore/api/cyberstorm/serializers/community.py(1 hunks)django/thunderstore/api/cyberstorm/tests/test_community_filters.py(1 hunks)django/thunderstore/community/admin/package_category.py(1 hunks)django/thunderstore/community/api/experimental/serializers.py(1 hunks)django/thunderstore/community/migrations/0038_add_packagecategory_hidden.py(1 hunks)django/thunderstore/community/models/package_category.py(1 hunks)django/thunderstore/community/tests/test_package_category_queryset.py(1 hunks)django/thunderstore/frontend/api/experimental/tests/test_community_package_list.py(1 hunks)django/thunderstore/frontend/api/experimental/tests/test_package_details.py(2 hunks)django/thunderstore/frontend/api/experimental/views/community_package_list.py(2 hunks)django/thunderstore/frontend/api/experimental/views/package_details.py(1 hunks)django/thunderstore/repository/views/package/detail.py(1 hunks)django/thunderstore/schema_import/schema.py(1 hunks)django/thunderstore/schema_import/sync.py(1 hunks)django/thunderstore/schema_import/tests/test_sync.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (9)
django/thunderstore/community/tests/test_package_category_queryset.py (2)
django/thunderstore/community/factories.py (3)
CommunityFactory(18-23)PackageListingFactory(43-102)categories(97-102)django/thunderstore/community/models/package_category.py (2)
PackageCategory(15-36)visible(7-8)
django/thunderstore/frontend/api/experimental/views/package_details.py (3)
django/thunderstore/frontend/api/experimental/serializers/views.py (1)
PackageCategorySerializer(39-42)django/thunderstore/community/factories.py (1)
categories(97-102)django/thunderstore/community/models/package_category.py (1)
visible(7-8)
django/thunderstore/api/cyberstorm/tests/test_community_filters.py (2)
django/thunderstore/community/factories.py (1)
PackageCategoryFactory(26-32)django/thunderstore/api/cyberstorm/views/community_filters.py (1)
get(33-42)
django/thunderstore/schema_import/tests/test_sync.py (4)
django/thunderstore/schema_import/schema.py (2)
Schema(41-47)SchemaCommunity(17-24)django/thunderstore/community/factories.py (1)
categories(97-102)django/thunderstore/schema_import/sync.py (1)
import_schema_communities(100-103)django/thunderstore/frontend/api/experimental/views/community_package_list.py (1)
get(38-57)
django/thunderstore/frontend/api/experimental/views/community_package_list.py (1)
django/thunderstore/community/models/package_category.py (1)
visible(7-8)
django/thunderstore/community/models/package_category.py (1)
django/thunderstore/core/mixins.py (1)
TimestampMixin(12-25)
django/thunderstore/repository/views/package/detail.py (2)
django/thunderstore/community/factories.py (1)
categories(97-102)django/thunderstore/community/models/package_category.py (1)
visible(7-8)
builder/src/components/CommunitySelector.tsx (1)
django/thunderstore/community/models/package_category.py (1)
visible(7-8)
django/thunderstore/frontend/api/experimental/tests/test_package_details.py (4)
django/thunderstore/community/models/package_category.py (2)
PackageCategory(15-36)visible(7-8)django/thunderstore/community/models/package_listing.py (1)
PackageListing(62-437)django/conftest.py (1)
api_client(442-443)django/thunderstore/community/factories.py (2)
PackageListingFactory(43-102)categories(97-102)
🪛 Flake8 (7.3.0)
django/thunderstore/frontend/api/experimental/tests/test_community_package_list.py
[error] 484-484: redefinition of unused 'reverse' from line 4
(F811)
[error] 487-487: redefinition of unused 'CacheBustCondition' from line 7
(F811)
[error] 488-488: redefinition of unused 'invalidate_cache' from line 8
(F811)
[error] 489-489: redefinition of unused 'PackageListingReviewStatus' from line 9
(F811)
[error] 490-490: redefinition of unused 'CommunitySiteFactory' from line 10
(F811)
[error] 490-490: redefinition of unused 'PackageListingFactory' from line 10
(F811)
[error] 491-491: redefinition of unused 'PackageCategory' from line 11
(F811)
[error] 491-491: redefinition of unused 'PackageListingSection' from line 11
(F811)
[error] 493-493: redefinition of unused 'CommunityPackageListApiView' from line 13
(F811)
[error] 494-494: redefinition of unused 'PackageRatingFactory' from line 14
(F811)
[error] 494-494: redefinition of unused 'PackageVersionFactory' from line 14
(F811)
[error] 501-501: redefinition of unused 'clear_pagination_cache' from line 21
(F811)
[error] 506-506: redefinition of unused 'test_only_packages_listed_in_community_are_returned' from line 26
(F811)
[error] 516-516: redefinition of unused 'test_only_active_packages_are_returned' from line 36
(F811)
[error] 526-526: redefinition of unused 'test_only_community_listings_for_correct_community_are_included_in_queryset' from line 46
(F811)
[error] 555-555: redefinition of unused 'test_rejected_packages_are_not_returned' from line 75
(F811)
[error] 572-572: redefinition of unused 'test_only_approved_packages_are_returned_when_approval_is_required' from line 92
(F811)
[error] 593-593: redefinition of unused 'test_deprecated_packages_are_returned_only_when_requested' from line 113
(F811)
[error] 611-611: redefinition of unused 'test_nsfw_packages_are_returned_only_when_requested' from line 131
(F811)
[error] 625-625: redefinition of unused 'test_packages_are_filtered_by_required_categories' from line 145
(F811)
[error] 653-653: redefinition of unused 'test_packages_are_filtered_by_excluded_categories' from line 173
(F811)
[error] 681-681: redefinition of unused 'test_packages_are_filtered_by_sections' from line 201
(F811)
[error] 708-708: redefinition of unused 'test_packages_are_filtered_by_query' from line 228
(F811)
[error] 736-736: redefinition of unused 'test_packages_are_ordered_by_update_date_by_default' from line 256
(F811)
[error] 757-757: redefinition of unused 'test_packages_can_be_ordered_by_creation_date' from line 277
(F811)
[error] 778-778: redefinition of unused 'test_paginated_results_are_cached' from line 298
(F811)
[error] 815-815: local variable 'listing' is assigned to but never used
(F841)
[error] 831-831: redefinition of unused '__query_api' from line 471
(F811)
[error] 845-845: redefinition of unused '__assert_packages_by_listings' from line 451
(F811)
🪛 Ruff (0.14.0)
django/thunderstore/community/migrations/0038_add_packagecategory_hidden.py
6-8: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
10-16: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
django/thunderstore/frontend/api/experimental/tests/test_community_package_list.py
484-484: Redefinition of unused reverse from line 4
Remove definition: reverse
(F811)
487-487: Redefinition of unused CacheBustCondition from line 7
Remove definition: CacheBustCondition
(F811)
488-488: Redefinition of unused invalidate_cache from line 8
Remove definition: invalidate_cache
(F811)
489-489: Redefinition of unused PackageListingReviewStatus from line 9
Remove definition: PackageListingReviewStatus
(F811)
490-490: Redefinition of unused CommunitySiteFactory from line 10
Remove definition: CommunitySiteFactory
(F811)
490-490: Redefinition of unused PackageListingFactory from line 10
Remove definition: PackageListingFactory
(F811)
491-491: Redefinition of unused PackageCategory from line 11
Remove definition: PackageCategory
(F811)
491-491: Redefinition of unused PackageListingSection from line 11
Remove definition: PackageListingSection
(F811)
493-493: Redefinition of unused CommunityPackageListApiView from line 13
Remove definition: CommunityPackageListApiView
(F811)
495-495: Redefinition of unused PackageRatingFactory from line 15
Remove definition: PackageRatingFactory
(F811)
496-496: Redefinition of unused PackageVersionFactory from line 16
Remove definition: PackageVersionFactory
(F811)
501-501: Redefinition of unused clear_pagination_cache from line 21
(F811)
506-506: Redefinition of unused test_only_packages_listed_in_community_are_returned from line 26
(F811)
516-516: Redefinition of unused test_only_active_packages_are_returned from line 36
(F811)
526-526: Redefinition of unused test_only_community_listings_for_correct_community_are_included_in_queryset from line 46
(F811)
555-555: Redefinition of unused test_rejected_packages_are_not_returned from line 75
(F811)
572-572: Redefinition of unused test_only_approved_packages_are_returned_when_approval_is_required from line 92
(F811)
593-593: Redefinition of unused test_deprecated_packages_are_returned_only_when_requested from line 113
(F811)
611-611: Redefinition of unused test_nsfw_packages_are_returned_only_when_requested from line 131
(F811)
625-625: Redefinition of unused test_packages_are_filtered_by_required_categories from line 145
(F811)
653-653: Redefinition of unused test_packages_are_filtered_by_excluded_categories from line 173
(F811)
681-681: Redefinition of unused test_packages_are_filtered_by_sections from line 201
(F811)
708-708: Redefinition of unused test_packages_are_filtered_by_query from line 228
(F811)
736-736: Redefinition of unused test_packages_are_ordered_by_update_date_by_default from line 256
(F811)
757-757: Redefinition of unused test_packages_can_be_ordered_by_creation_date from line 277
(F811)
778-778: Redefinition of unused test_paginated_results_are_cached from line 298
(F811)
815-815: Local variable listing is assigned to but never used
Remove assignment to unused variable listing
(F841)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
- GitHub Check: Test pytest (1)
- GitHub Check: Test pytest (4)
- GitHub Check: Test pytest (3)
- GitHub Check: Test pytest (6)
- GitHub Check: Test pytest (2)
- GitHub Check: Test pytest (5)
- GitHub Check: Test missing migrations
- GitHub Check: Analyze (javascript)
- GitHub Check: Analyze (python)
- GitHub Check: Build docker image
🔇 Additional comments (19)
builder/src/api/models.ts (1)
91-91: LGTM!The addition of the
hiddenfield to thePackageCategoryinterface is consistent with the backend serializer changes.django/thunderstore/api/cyberstorm/serializers/community.py (1)
47-47: LGTM!The
hiddenfield addition to the serializer is correct and aligns with the model changes.django/thunderstore/schema_import/schema.py (1)
14-14: LGTM!The default value of
Falseensures backwards compatibility with existing schema files that don't include thehiddenfield.django/thunderstore/schema_import/sync.py (1)
72-72: LGTM!Using
getattrwith a default value ofFalseensures backwards compatibility when importing schemas that don't include thehiddenfield.builder/src/components/CommunitySelector.tsx (1)
27-29: LGTM!The filtering logic correctly excludes hidden categories from the UI dropdown, and the comment clearly explains the intent.
django/thunderstore/community/api/experimental/serializers.py (1)
28-28: LGTM!The
hiddenfield addition to the experimental serializer is consistent with the changes in the Cyberstorm serializer.django/thunderstore/frontend/api/experimental/views/community_package_list.py (2)
248-248: LGTM!The change correctly filters out hidden categories from the per-package category list.
273-273: LGTM!The change correctly filters out hidden categories from the community-level category list. This is consistent with the filtering applied at line 248 for per-package categories.
django/thunderstore/frontend/api/experimental/views/package_details.py (1)
113-114: Approve change—visible()implementation verified
Thevisible()method onPackageCategoryQuerySetcorrectly filtershidden=Falseand is covered by tests.django/thunderstore/repository/views/package/detail.py (1)
154-160: LGTM! Correct filtering for management panel.Both category lists now properly filter to visible categories using
.visible(). This ensures hidden categories don't appear in the package management interface.django/thunderstore/community/migrations/0038_add_packagecategory_hidden.py (1)
1-16: LGTM! Clean migration with proper defaults.The migration correctly adds the
hiddenfield withdefault=False, ensuring existing categories remain visible. The dependency chain is proper.Note: The Ruff warnings about
ClassVarare false positives - Django migration class attributes (dependencies,operations) don't require type annotations.django/thunderstore/schema_import/tests/test_sync.py (1)
98-123: LGTM! Comprehensive test for hidden flag import.The test properly validates that the schema import flow preserves the
hiddenflag for both visible and hidden categories. Good use of dict comprehension for lookup.django/thunderstore/api/cyberstorm/tests/test_community_filters.py (1)
13-25: LGTM! API correctly returns all categories with hidden flag.The test validates that the cyberstorm filters API returns both visible and hidden categories, with the
hiddenfield properly serialized. This is the correct behavior for this endpoint - it provides complete data and lets the frontend filter as needed.django/thunderstore/community/admin/package_category.py (1)
8-13: LGTM! Proper admin exposure of hidden field.The
hiddenfield is now filterable and visible in the admin list view, enabling admins to easily manage category visibility.django/thunderstore/community/tests/test_package_category_queryset.py (1)
1-51: LGTM! Comprehensive queryset visibility tests.Excellent test coverage validating
visible()filtering across all usage patterns:
- Base queryset (
PackageCategory.objects.visible())- Reverse ForeignKey relation (
community.package_categories.visible())- ManyToMany relation (
listing.categories.visible())All tests properly validate that hidden categories are excluded and visible categories are included.
django/thunderstore/frontend/api/experimental/tests/test_package_details.py (1)
213-235: LGTM! Validates hidden category exclusion in package details.The test properly verifies that the package detail API endpoint excludes hidden categories from the response, even when they're associated with the listing. Clean test structure and assertions.
django/thunderstore/community/models/package_category.py (3)
6-8: LGTM! Clean queryset implementation.The
visible()filter correctly returns categories wherehidden=False. The method is chainable and returns a queryset, allowing further filtering or ordering as needed.
11-12: LGTM! Proper manager setup.Using
Manager.from_queryset()ensures thevisible()method is available on both the base manager and related managers (reverse ForeignKey and ManyToMany), which is validated by the tests.Also applies to: 16-16
24-24: LGTM! Sensible default for backward compatibility.The
default=Falseensures existing categories remain visible after migration, maintaining backward compatibility while enabling the new visibility control feature.
| from typing import Dict, List, Union | ||
|
|
||
| import pytest | ||
| from django.urls import reverse | ||
| from rest_framework.test import APIClient | ||
|
|
||
| from thunderstore.cache.enums import CacheBustCondition | ||
| from thunderstore.cache.tasks import invalidate_cache | ||
| from thunderstore.community.consts import PackageListingReviewStatus | ||
| from thunderstore.community.factories import CommunitySiteFactory, PackageListingFactory | ||
| from thunderstore.community.models import PackageCategory, PackageListingSection | ||
| from thunderstore.community.models.package_listing import PackageListing | ||
| from thunderstore.frontend.api.experimental.views import CommunityPackageListApiView | ||
| from thunderstore.repository.factories import ( | ||
| PackageRatingFactory, | ||
| PackageVersionFactory, | ||
| ) | ||
|
|
||
|
|
||
| @pytest.fixture(scope="module", autouse=True) | ||
| def clear_pagination_cache(): | ||
| invalidate_cache(CacheBustCondition.any_package_updated) | ||
|
|
||
|
|
||
| @pytest.mark.django_db | ||
| def test_only_packages_listed_in_community_are_returned(api_client: APIClient) -> None: | ||
| listing1 = PackageListingFactory() | ||
| PackageListingFactory() | ||
|
|
||
| data = __query_api(api_client, listing1.community.identifier) | ||
|
|
||
| __assert_packages_by_listings(data, listing1) | ||
|
|
||
|
|
||
| @pytest.mark.django_db | ||
| def test_only_active_packages_are_returned(api_client: APIClient) -> None: | ||
| listing1 = PackageListingFactory(package_kwargs={"is_active": False}) | ||
| PackageListingFactory() | ||
|
|
||
| data = __query_api(api_client, listing1.community.identifier) | ||
|
|
||
| assert len(data["packages"]) == 0 | ||
|
|
||
|
|
||
| @pytest.mark.django_db | ||
| def test_only_community_listings_for_correct_community_are_included_in_queryset() -> None: | ||
| """ | ||
| Due to the implementation, test case | ||
| test_only_packages_listed_in_community_are_returned might | ||
| incorrectly pass sometimes when data is returned from the database | ||
| in a fortunate order. That test case still has its uses, since this | ||
| one only checks a small part of the data fetching implementation. | ||
| """ | ||
| listing1 = PackageListingFactory() | ||
| listing2 = PackageListingFactory(package_=listing1.package) | ||
|
|
||
| qs1 = CommunityPackageListApiView().get_queryset(listing1.community) | ||
| qs2 = CommunityPackageListApiView().get_queryset(listing2.community) | ||
|
|
||
| assert len(qs1.all()) == 1 | ||
| assert len(qs1.all()[0].community_listings.all()) == 1 | ||
| assert ( | ||
| qs1.all()[0].community_listings.all()[0].community.name | ||
| == listing1.community.name | ||
| ) | ||
| assert len(qs2.all()) == 1 | ||
| assert len(qs2.all()[0].community_listings.all()) == 1 | ||
| assert ( | ||
| qs2.all()[0].community_listings.all()[0].community.name | ||
| == listing2.community.name | ||
| ) | ||
|
|
||
|
|
||
| @pytest.mark.django_db | ||
| def test_rejected_packages_are_not_returned(api_client: APIClient) -> None: | ||
| listing1 = PackageListingFactory( | ||
| review_status=PackageListingReviewStatus.unreviewed | ||
| ) | ||
| listing2 = PackageListingFactory( | ||
| community_=listing1.community, review_status=PackageListingReviewStatus.approved | ||
| ) | ||
| PackageListingFactory( | ||
| community_=listing1.community, review_status=PackageListingReviewStatus.rejected | ||
| ) | ||
|
|
||
| data = __query_api(api_client, listing1.community.identifier) | ||
|
|
||
| __assert_packages_by_listings(data, [listing2, listing1]) | ||
|
|
||
|
|
||
| @pytest.mark.django_db | ||
| def test_only_approved_packages_are_returned_when_approval_is_required( | ||
| api_client: APIClient, | ||
| ) -> None: | ||
| listing1 = PackageListingFactory( | ||
| review_status=PackageListingReviewStatus.unreviewed | ||
| ) | ||
| listing1.community.require_package_listing_approval = True | ||
| listing1.community.save() | ||
| listing2 = PackageListingFactory( | ||
| community_=listing1.community, review_status=PackageListingReviewStatus.approved | ||
| ) | ||
| PackageListingFactory( | ||
| community_=listing1.community, review_status=PackageListingReviewStatus.rejected | ||
| ) | ||
|
|
||
| data = __query_api(api_client, listing1.community.identifier) | ||
|
|
||
| __assert_packages_by_listings(data, listing2) | ||
|
|
||
|
|
||
| @pytest.mark.django_db | ||
| def test_deprecated_packages_are_returned_only_when_requested( | ||
| api_client: APIClient, | ||
| ) -> None: | ||
| active = PackageListingFactory() | ||
| PackageListingFactory( | ||
| community_=active.community, package_kwargs={"is_deprecated": True} | ||
| ) | ||
|
|
||
| data = __query_api(api_client, active.community.identifier) | ||
|
|
||
| __assert_packages_by_listings(data, active) | ||
|
|
||
| data = __query_api(api_client, active.community.identifier, "deprecated=on") | ||
|
|
||
| assert len(data["packages"]) == 2 | ||
|
|
||
|
|
||
| @pytest.mark.django_db | ||
| def test_nsfw_packages_are_returned_only_when_requested(api_client: APIClient) -> None: | ||
| sfw = PackageListingFactory() | ||
| PackageListingFactory(community_=sfw.community, has_nsfw_content=True) | ||
|
|
||
| data = __query_api(api_client, sfw.community.identifier) | ||
|
|
||
| __assert_packages_by_listings(data, sfw) | ||
|
|
||
| data = __query_api(api_client, sfw.community.identifier, "nsfw=true") | ||
|
|
||
| assert len(data["packages"]) == 2 | ||
|
|
||
|
|
||
| @pytest.mark.django_db | ||
| def test_packages_are_filtered_by_required_categories(api_client: APIClient) -> None: | ||
| site = CommunitySiteFactory() | ||
| cat1 = PackageCategory.objects.create( | ||
| name="c1", slug="c1", community=site.community | ||
| ) | ||
| cat2 = PackageCategory.objects.create( | ||
| name="c2", slug="c2", community=site.community | ||
| ) | ||
| PackageListingFactory(community_=site.community) | ||
| pl2 = PackageListingFactory(community_=site.community, categories=[cat1]) | ||
| pl3 = PackageListingFactory(community_=site.community, categories=[cat2]) | ||
|
|
||
| data = __query_api( | ||
| api_client, site.community.identifier, f"included_categories={cat1.slug}" | ||
| ) | ||
|
|
||
| __assert_packages_by_listings(data, pl2) | ||
|
|
||
| data = __query_api( | ||
| api_client, | ||
| site.community.identifier, | ||
| f"included_categories={cat1.slug}&included_categories={cat2.slug}", | ||
| ) | ||
|
|
||
| __assert_packages_by_listings(data, [pl3, pl2]) | ||
|
|
||
|
|
||
| @pytest.mark.django_db | ||
| def test_packages_are_filtered_by_excluded_categories(api_client: APIClient) -> None: | ||
| site = CommunitySiteFactory() | ||
| cat1 = PackageCategory.objects.create( | ||
| name="c1", slug="c1", community=site.community | ||
| ) | ||
| cat2 = PackageCategory.objects.create( | ||
| name="c2", slug="c2", community=site.community | ||
| ) | ||
| pl1 = PackageListingFactory(community_=site.community) | ||
| PackageListingFactory(community_=site.community, categories=[cat1]) | ||
| pl3 = PackageListingFactory(community_=site.community, categories=[cat2]) | ||
|
|
||
| data = __query_api( | ||
| api_client, site.community.identifier, f"excluded_categories={cat1.slug}" | ||
| ) | ||
|
|
||
| __assert_packages_by_listings(data, [pl3, pl1]) | ||
|
|
||
| data = __query_api( | ||
| api_client, | ||
| site.community.identifier, | ||
| f"excluded_categories={cat1.slug}&excluded_categories={cat2.slug}", | ||
| ) | ||
|
|
||
| __assert_packages_by_listings(data, pl1) | ||
|
|
||
|
|
||
| @pytest.mark.django_db | ||
| def test_packages_are_filtered_by_sections(api_client: APIClient) -> None: | ||
| site = CommunitySiteFactory() | ||
| required = PackageCategory.objects.create( | ||
| name="r", slug="r", community=site.community | ||
| ) | ||
| excluded = PackageCategory.objects.create( | ||
| name="e", slug="e", community=site.community | ||
| ) | ||
| irrelevant = PackageCategory.objects.create( | ||
| name="i", slug="i", community=site.community | ||
| ) | ||
| section = PackageListingSection.objects.create( | ||
| name="Modpacks", slug="modpacks", community=site.community | ||
| ) | ||
| section.require_categories.set([required]) | ||
| section.exclude_categories.set([excluded]) | ||
| expected = PackageListingFactory(community_=site.community, categories=[required]) | ||
| PackageListingFactory(community_=site.community, categories=[required, excluded]) | ||
| PackageListingFactory(community_=site.community, categories=[excluded]) | ||
| PackageListingFactory(community_=site.community, categories=[irrelevant]) | ||
|
|
||
| data = __query_api(api_client, site.community.identifier, f"section={section.slug}") | ||
|
|
||
| __assert_packages_by_listings(data, expected) | ||
|
|
||
|
|
||
| @pytest.mark.django_db | ||
| def test_packages_are_filtered_by_query(api_client: APIClient) -> None: | ||
| site = CommunitySiteFactory() | ||
| listing1 = PackageListingFactory(community_=site.community) | ||
| listing2 = PackageListingFactory(community_=site.community) | ||
| listing3 = PackageListingFactory(community_=site.community) | ||
|
|
||
| data = __query_api( | ||
| api_client, site.community.identifier, f"q={listing1.package.name}" | ||
| ) | ||
|
|
||
| __assert_packages_by_listings(data, listing1) | ||
|
|
||
| data = __query_api( | ||
| api_client, site.community.identifier, f"q={listing2.package.owner.name}" | ||
| ) | ||
|
|
||
| __assert_packages_by_listings(data, listing2) | ||
|
|
||
| data = __query_api( | ||
| api_client, | ||
| site.community.identifier, | ||
| f"q={listing3.package.latest.description}", | ||
| ) | ||
|
|
||
| __assert_packages_by_listings(data, listing3) | ||
|
|
||
|
|
||
| @pytest.mark.django_db | ||
| def test_packages_are_ordered_by_update_date_by_default(api_client: APIClient) -> None: | ||
| site = CommunitySiteFactory() | ||
| listing1 = PackageListingFactory( | ||
| community_=site.community, | ||
| package_kwargs={"date_updated": "2022-02-02 01:23:45Z"}, | ||
| ) | ||
| listing2 = PackageListingFactory( | ||
| community_=site.community, | ||
| package_kwargs={"date_updated": "2022-02-22 01:23:45Z"}, | ||
| ) | ||
| listing3 = PackageListingFactory( | ||
| community_=site.community, | ||
| package_kwargs={"date_updated": "2022-02-12 01:23:45Z"}, | ||
| ) | ||
|
|
||
| data = __query_api(api_client, site.community.identifier) | ||
|
|
||
| __assert_packages_by_listings(data, [listing2, listing3, listing1]) | ||
|
|
||
|
|
||
| @pytest.mark.django_db | ||
| def test_packages_can_be_ordered_by_creation_date(api_client: APIClient) -> None: | ||
| site = CommunitySiteFactory() | ||
| listing1 = PackageListingFactory( | ||
| community_=site.community, | ||
| package_kwargs={"date_created": "2022-02-12 01:23:45Z"}, | ||
| ) | ||
| listing2 = PackageListingFactory( | ||
| community_=site.community, | ||
| package_kwargs={"date_created": "2022-02-22 01:23:45Z"}, | ||
| ) | ||
| listing3 = PackageListingFactory( | ||
| community_=site.community, | ||
| package_kwargs={"date_created": "2022-02-02 01:23:45Z"}, | ||
| ) | ||
|
|
||
| data = __query_api(api_client, site.community.identifier, "ordering=newest") | ||
|
|
||
| __assert_packages_by_listings(data, [listing2, listing1, listing3]) | ||
|
|
||
|
|
||
| @pytest.mark.django_db | ||
| def test_paginated_results_are_cached(api_client: APIClient) -> None: | ||
| # First request should fetch fresh data. | ||
| listing = PackageListingFactory(package_version_kwargs={"downloads": 1}) | ||
|
|
||
| data = __query_api(api_client, listing.community.identifier) | ||
|
|
||
| assert len(data["packages"]) == 1 | ||
| assert data["packages"][0]["download_count"] == 1 | ||
|
|
||
| # Changes shouldn't be returned until cache is busted. | ||
| listing.package.latest.downloads = 3 | ||
| listing.package.latest.save() | ||
|
|
||
| data = __query_api(api_client, listing.community.identifier) | ||
|
|
||
| assert len(data["packages"]) == 1 | ||
| assert data["packages"][0]["download_count"] == 1 | ||
|
|
||
| # Manual cache busting should update the results. | ||
| invalidate_cache(CacheBustCondition.any_package_updated) | ||
|
|
||
| data = __query_api(api_client, listing.community.identifier) | ||
|
|
||
| assert len(data["packages"]) == 1 | ||
| assert data["packages"][0]["download_count"] == 3 | ||
|
|
||
|
|
||
| @pytest.mark.django_db | ||
| def test_hidden_categories_are_excluded_from_ui_lists(api_client: APIClient) -> None: | ||
| site = CommunitySiteFactory() | ||
| visible = PackageCategory.objects.create( | ||
| name="visible", slug="visible", community=site.community, hidden=False | ||
| ) | ||
| hidden = PackageCategory.objects.create( | ||
| name="hidden", slug="hidden", community=site.community, hidden=True | ||
| ) | ||
| # listing with both categories | ||
| listing = PackageListingFactory( | ||
| community_=site.community, categories=[visible, hidden] | ||
| ) | ||
|
|
||
| data = __query_api(api_client, site.community.identifier) | ||
|
|
||
| # Top-level categories should exclude hidden | ||
| top_categories = data["categories"] | ||
| assert {c["slug"] for c in top_categories} == {"visible"} | ||
|
|
||
| # Package card categories should exclude hidden | ||
| assert len(data["packages"]) == 1 | ||
| pkg_categories = data["packages"][0]["categories"] | ||
| assert {c["slug"] for c in pkg_categories} == {"visible"} | ||
|
|
||
|
|
||
| def __query_api( | ||
| client: APIClient, community_identifier: str, query_params: str = "" | ||
| ) -> Dict[str, Union[str, int, bool, List[Dict]]]: | ||
| url = reverse( | ||
| "api:experimental:frontend.community.packages", | ||
| kwargs={"community_identifier": community_identifier}, | ||
| ) | ||
| if query_params: | ||
| url = f"{url}?{query_params}" | ||
| response = client.get(url) | ||
| assert response.status_code == 200 | ||
| return response.json() | ||
|
|
||
|
|
||
| def __assert_packages_by_listings(data, listings): | ||
| if not isinstance(listings, list): | ||
| listings = [listings] | ||
|
|
||
| assert len(listings) == len(data["packages"]) | ||
| for index, expected in enumerate(listings): |
There was a problem hiding this comment.
Remove duplicated module block
Starting at Line 481 the entire module—including imports, fixtures, helper functions, and every test—is copied verbatim and redefined. This triggers Ruff/Flake8 F811 (function redefinition) across the board and will fail linting/CI. Drop the first copy and keep only the intended final definitions (including the new hidden-category test) before merging.
🧰 Tools
🪛 Flake8 (7.3.0)
[error] 484-484: redefinition of unused 'reverse' from line 4
(F811)
[error] 487-487: redefinition of unused 'CacheBustCondition' from line 7
(F811)
[error] 488-488: redefinition of unused 'invalidate_cache' from line 8
(F811)
[error] 489-489: redefinition of unused 'PackageListingReviewStatus' from line 9
(F811)
[error] 490-490: redefinition of unused 'CommunitySiteFactory' from line 10
(F811)
[error] 490-490: redefinition of unused 'PackageListingFactory' from line 10
(F811)
[error] 491-491: redefinition of unused 'PackageCategory' from line 11
(F811)
[error] 491-491: redefinition of unused 'PackageListingSection' from line 11
(F811)
[error] 493-493: redefinition of unused 'CommunityPackageListApiView' from line 13
(F811)
[error] 494-494: redefinition of unused 'PackageRatingFactory' from line 14
(F811)
[error] 494-494: redefinition of unused 'PackageVersionFactory' from line 14
(F811)
[error] 501-501: redefinition of unused 'clear_pagination_cache' from line 21
(F811)
[error] 506-506: redefinition of unused 'test_only_packages_listed_in_community_are_returned' from line 26
(F811)
[error] 516-516: redefinition of unused 'test_only_active_packages_are_returned' from line 36
(F811)
[error] 526-526: redefinition of unused 'test_only_community_listings_for_correct_community_are_included_in_queryset' from line 46
(F811)
[error] 555-555: redefinition of unused 'test_rejected_packages_are_not_returned' from line 75
(F811)
[error] 572-572: redefinition of unused 'test_only_approved_packages_are_returned_when_approval_is_required' from line 92
(F811)
[error] 593-593: redefinition of unused 'test_deprecated_packages_are_returned_only_when_requested' from line 113
(F811)
[error] 611-611: redefinition of unused 'test_nsfw_packages_are_returned_only_when_requested' from line 131
(F811)
[error] 625-625: redefinition of unused 'test_packages_are_filtered_by_required_categories' from line 145
(F811)
[error] 653-653: redefinition of unused 'test_packages_are_filtered_by_excluded_categories' from line 173
(F811)
[error] 681-681: redefinition of unused 'test_packages_are_filtered_by_sections' from line 201
(F811)
[error] 708-708: redefinition of unused 'test_packages_are_filtered_by_query' from line 228
(F811)
[error] 736-736: redefinition of unused 'test_packages_are_ordered_by_update_date_by_default' from line 256
(F811)
[error] 757-757: redefinition of unused 'test_packages_can_be_ordered_by_creation_date' from line 277
(F811)
[error] 778-778: redefinition of unused 'test_paginated_results_are_cached' from line 298
(F811)
[error] 815-815: local variable 'listing' is assigned to but never used
(F841)
[error] 831-831: redefinition of unused '__query_api' from line 471
(F811)
[error] 845-845: redefinition of unused '__assert_packages_by_listings' from line 451
(F811)
🪛 Ruff (0.14.0)
484-484: Redefinition of unused reverse from line 4
Remove definition: reverse
(F811)
487-487: Redefinition of unused CacheBustCondition from line 7
Remove definition: CacheBustCondition
(F811)
488-488: Redefinition of unused invalidate_cache from line 8
Remove definition: invalidate_cache
(F811)
489-489: Redefinition of unused PackageListingReviewStatus from line 9
Remove definition: PackageListingReviewStatus
(F811)
490-490: Redefinition of unused CommunitySiteFactory from line 10
Remove definition: CommunitySiteFactory
(F811)
490-490: Redefinition of unused PackageListingFactory from line 10
Remove definition: PackageListingFactory
(F811)
491-491: Redefinition of unused PackageCategory from line 11
Remove definition: PackageCategory
(F811)
491-491: Redefinition of unused PackageListingSection from line 11
Remove definition: PackageListingSection
(F811)
493-493: Redefinition of unused CommunityPackageListApiView from line 13
Remove definition: CommunityPackageListApiView
(F811)
495-495: Redefinition of unused PackageRatingFactory from line 15
Remove definition: PackageRatingFactory
(F811)
496-496: Redefinition of unused PackageVersionFactory from line 16
Remove definition: PackageVersionFactory
(F811)
501-501: Redefinition of unused clear_pagination_cache from line 21
(F811)
506-506: Redefinition of unused test_only_packages_listed_in_community_are_returned from line 26
(F811)
516-516: Redefinition of unused test_only_active_packages_are_returned from line 36
(F811)
526-526: Redefinition of unused test_only_community_listings_for_correct_community_are_included_in_queryset from line 46
(F811)
555-555: Redefinition of unused test_rejected_packages_are_not_returned from line 75
(F811)
572-572: Redefinition of unused test_only_approved_packages_are_returned_when_approval_is_required from line 92
(F811)
593-593: Redefinition of unused test_deprecated_packages_are_returned_only_when_requested from line 113
(F811)
611-611: Redefinition of unused test_nsfw_packages_are_returned_only_when_requested from line 131
(F811)
625-625: Redefinition of unused test_packages_are_filtered_by_required_categories from line 145
(F811)
653-653: Redefinition of unused test_packages_are_filtered_by_excluded_categories from line 173
(F811)
681-681: Redefinition of unused test_packages_are_filtered_by_sections from line 201
(F811)
708-708: Redefinition of unused test_packages_are_filtered_by_query from line 228
(F811)
736-736: Redefinition of unused test_packages_are_ordered_by_update_date_by_default from line 256
(F811)
757-757: Redefinition of unused test_packages_can_be_ordered_by_creation_date from line 277
(F811)
778-778: Redefinition of unused test_paginated_results_are_cached from line 298
(F811)
815-815: Local variable listing is assigned to but never used
Remove assignment to unused variable listing
(F841)
🤖 Prompt for AI Agents
In
django/thunderstore/frontend/api/experimental/tests/test_community_package_list.py
around lines 481 to 850 there is a duplicated module block (imports, fixtures,
helper functions, and tests) causing redefinition lint errors; remove the
earlier/first copy so only one set of imports, fixtures, helpers, and tests
remains (keep the intended final definitions including the new hidden-category
test), ensure there are no leftover duplicate names or imports, and run the
linter/tests to verify F811 is resolved.
Add a new
hiddenproperty to thePackageCategorymodel and disable rendering of hidden categories on the UI side.Include the property in APIs that return categories as objects so frontend can be updated to exclude them.