Skip to content

Add hidden propert to PackageCategory model#1190

Draft
MythicManiac wants to merge 2 commits intomasterfrom
package-category-hidden
Draft

Add hidden propert to PackageCategory model#1190
MythicManiac wants to merge 2 commits intomasterfrom
package-category-hidden

Conversation

@MythicManiac
Copy link
Copy Markdown
Member

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.

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.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Oct 14, 2025

Walkthrough

This 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)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.82% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly captures the primary change of adding a hidden property to the PackageCategory model and directly relates to the core modifications in the pull request. Although it contains a minor spelling error, the intent remains clear.
Description Check ✅ Passed The pull request description accurately reflects the addition of the hidden property to the PackageCategory model and outlines how hidden categories will be excluded from the UI and included in APIs, aligning with the actual changes in the diff.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch package-category-hidden

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov bot commented Oct 14, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.84%. Comparing base (1ba470a) to head (a86d53d).

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ba470a and a86d53d.

📒 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 hidden field to the PackageCategory interface is consistent with the backend serializer changes.

django/thunderstore/api/cyberstorm/serializers/community.py (1)

47-47: LGTM!

The hidden field 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 False ensures backwards compatibility with existing schema files that don't include the hidden field.

django/thunderstore/schema_import/sync.py (1)

72-72: LGTM!

Using getattr with a default value of False ensures backwards compatibility when importing schemas that don't include the hidden field.

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 hidden field 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
The visible() method on PackageCategoryQuerySet correctly filters hidden=False and 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 hidden field with default=False, ensuring existing categories remain visible. The dependency chain is proper.

Note: The Ruff warnings about ClassVar are 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 hidden flag 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 hidden field 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 hidden field 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 where hidden=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 the visible() 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=False ensures existing categories remain visible after migration, maintaining backward compatibility while enabling the new visibility control feature.

Comment on lines +481 to +850
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):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

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.

1 participant