-
Notifications
You must be signed in to change notification settings - Fork 30
Implement Visibility for Listings / Versions #1112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
The Are there tests for the |
I've added tests, but I'm not too sure what kind of documentation would fit here. Any ideas? |
I was thinking a docstring would do here—just to document the basics, such as the precedence of how these visibility rules are handled. |
65291df
to
1d05852
Compare
521bf08
to
8496a98
Compare
Rework VisibilityMixin to take active() into account and use abstract methods Add VisibilityMixin and VisibilityQuerySet to PackageListing Add update_visibility and is_visible_to_user functions for PackageListing and PackageVersion Add review status to PackageVersion Add Listing and Version specific AuditAction enums
Update visibility when package saves so changing is_active properly updates visibility
Small update_visibility optimization, does not affect worst case
8496a98
to
dd38bd9
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1112 +/- ##
==========================================
+ Coverage 93.03% 93.07% +0.03%
==========================================
Files 321 324 +3
Lines 9675 10304 +629
Branches 857 1024 +167
==========================================
+ Hits 9001 9590 +589
- Misses 553 598 +45
+ Partials 121 116 -5 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
...go/thunderstore/community/migrations/0037_create_default_visibility_for_existing_listings.py
Outdated
Show resolved
Hide resolved
@transaction.atomic | ||
def update_visibility(self): | ||
""" | ||
Updates the version's visibility based on whether it or its package is active and its review status. | ||
This may also change the visibility of related listings, so it calls update_visibility() on them. | ||
|
||
By default, versions are visible to everyone (for now). Rejected versions aren't publicly visible, | ||
and inactive versions or versions with inactive packages aren't visible at all. | ||
""" | ||
original_visibility_bitstring = self.visibility.bitstring() | ||
|
||
self.visibility.public_detail = True | ||
self.visibility.public_list = True | ||
self.visibility.owner_detail = True | ||
self.visibility.owner_list = True | ||
self.visibility.moderator_detail = True | ||
self.visibility.moderator_list = True | ||
|
||
if not self.is_active or not self.package.is_active: | ||
self.visibility.public_detail = False | ||
self.visibility.public_list = False | ||
self.visibility.owner_detail = False | ||
self.visibility.owner_list = False | ||
self.visibility.moderator_detail = False | ||
self.visibility.moderator_list = False | ||
|
||
if ( | ||
self.review_status == PackageVersionReviewStatus.rejected | ||
or self.review_status == PackageVersionReviewStatus.pending | ||
): | ||
self.visibility.public_detail = False | ||
self.visibility.public_list = False | ||
|
||
if self.visibility.bitstring != original_visibility_bitstring: | ||
self.visibility.save() | ||
for listing in self.package.community_listings.all(): | ||
listing.update_visibility() | ||
self.package.recache_latest() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like we should think about how to build this in a way that's easy to test and maintain, if nothing else by splitting it to more atomic components that are easier to write standalone tests for rather than having to test everything at once.
Right now it seems like we're actually dealing with a pretty simple map of conditions that need to match
to flags that should be flipped to False
. If that's a paradigm we can assume is true, this shouldn't be too hard to turn into a system of sorts that functions in a more "data driven" fashion, making it easier to reliably test.
Child-updates & their propagation of course is a separate concern and probably fine as it is
This comment applies to the overall design of visibility updates and theri implementations in other places of the codebase too, not just this specific block
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should note that this approach does have some advantages (mainly simplicity to read), but I suspect a light abstraction layer could still prove useful long-term and can actually make it easier to reason about if you have the required contextual info. Reading through dozens of lines of code isn't as straight forward as seeing "if the package is not active, visibility rules of inactive packages are applied" in a single line after all. What those rules are isn't as relevant on this level of context in my opinion
Refactor AuditAction into AuditTarget & AuditAction
ba0f6fa
to
929a32b
Compare
...go/thunderstore/repository/migrations/0059_create_default_visibility_for_existing_records.py
Outdated
Show resolved
Hide resolved
for instance in PackageVersion.objects.filter(visibility__isnull=True): | ||
visibility_flags = VisibilityFlags.objects.create( | ||
public_list=False, | ||
public_detail=False, | ||
owner_list=True, | ||
owner_detail=True, | ||
moderator_list=True, | ||
moderator_detail=True, | ||
admin_list=True, | ||
admin_detail=True, | ||
) | ||
instance.visibility = visibility_flags | ||
instance.save() | ||
update_version_visibility(instance) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It appears that the migration 0059_create_default_visibility_for_existing_records.py
is attempting to set visibility fields for PackageVersion
objects, but there's no corresponding migration that adds the visibility
field to the PackageVersion
model.
Before this migration runs, a separate migration should be created to add the visibility
field to the PackageVersion
model, similar to how it was done for Package
in migration 0058_package_visibility.py
.
This would ensure that when update_version_visibility()
is called, the visibility
field actually exists on the model instances.
Spotted by Diamond
Is this helpful? React 👍 or 👎 to let us know.
Refactor update_visibility Add VisibilityMixin to Package Optimize Package visibility from versions querying Use tuple instead of bitstring for comparing to original visibility
929a32b
to
05a2da3
Compare
@@ -44,7 +45,7 @@ def get_package_dependants_list(package_pk: int): | |||
return list(get_package_dependants(package_pk)) | |||
|
|||
|
|||
class Package(AdminLinkMixin, models.Model): | |||
class Package(VisibilityMixin, AdminLinkMixin): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Package
class inherits from VisibilityMixin
which requires implementing the abstract method is_visible_to_user()
. This method is implemented in both PackageListing
and PackageVersion
classes, but is missing from Package
. Please add an implementation similar to the other classes to ensure proper visibility control for packages.
class Package(VisibilityMixin, AdminLinkMixin): | |
class Package(VisibilityMixin, AdminLinkMixin): | |
def is_visible_to_user(self, user=None): | |
# Implementation similar to PackageListing and PackageVersion | |
if user and user.is_staff: | |
return True | |
return self.is_active | |
Spotted by Diamond
Is this helpful? React 👍 or 👎 to let us know.
version.review_status = PackageVersionReviewStatus.rejected | ||
version.save(update_fields=("review_status",)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of update_fields=("review_status",)
in the admin actions bypasses the visibility update mechanism. When a version is rejected or approved through the admin interface, its visibility flags won't be updated because the VisibilityMixin.update_visibility()
method is only called during a full save operation.
Two options to fix this:
-
Remove the
update_fields
parameter to ensure the full save method runs:version.review_status = PackageVersionReviewStatus.rejected version.save()
-
Better approach: use the model methods that handle both status and visibility updates:
version.reject(request.user)
version.approve(request.user)
These methods already handle the review status change, visibility updates, and audit logging in a single transaction.
version.review_status = PackageVersionReviewStatus.rejected | |
version.save(update_fields=("review_status",)) | |
version.reject(request.user) |
Spotted by Diamond
Is this helpful? React 👍 or 👎 to let us know.
Rework VisibilityMixin to take active() into account and use abstract methods Add VisibilityMixin and VisibilityQuerySet to PackageListing Add update_visibility and is_visible_to_user functions for PackageListing and PackageVersion Add review status to PackageVersion
Add Listing and Version specific AuditAction enums
This PR implements Visibility, but none of our views or API endpoints use it, so nothing should change as far as what users see. Doing PRs in pieces like these will hopefully make review easier than looking over 60+ commits from this.