Skip to content
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

Update queryset references in views to avoid cached results #2582

Merged
merged 2 commits into from
Nov 6, 2024

Conversation

sainak
Copy link
Member

@sainak sainak commented Nov 5, 2024

Refactor queryset retrieval in various views to ensure fresh data is fetched instead of getting unexpected cached results.

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced permission checks for user actions across various viewsets, ensuring stricter access control.
    • Improved handling of CSV requests in the PatientExternalTestViewSet for better data export functionality.
  • Bug Fixes

    • Updated queryset retrieval methods to ensure dynamic filtering based on user permissions is consistently applied across all relevant viewsets.
  • Refactor

    • Streamlined the logic for fetching and filtering querysets in multiple viewsets, enhancing code clarity and maintainability.
    • Updated error handling in the get_object method of the UserViewSet to provide clearer feedback when a user is not found.

Copy link

coderabbitai bot commented Nov 5, 2024

Walkthrough

The pull request introduces updates across several viewsets in the application, primarily focusing on the get_queryset method in multiple classes. These changes enhance how the initial queryset is retrieved, often utilizing super().get_queryset() to leverage parent class logic. Additionally, modifications include improved permission checks in methods like destroy and enhanced handling of CSV requests and validation in specific viewsets. Overall, the changes aim to streamline data access and maintain consistent permission handling.

Changes

File Path Change Summary
care/facility/api/viewsets/mixins/access.py Updated UserAccessMixin and AssetUserAccessMixin: modified get_queryset, filter_by_user_scope, and get_authenticators methods.
care/facility/api/viewsets/patient_consultation.py Refactored get_queryset in PatientConsultationViewSet to use a local variable instead of self.queryset.
care/facility/api/viewsets/patient_external_test.py Updated PatientExternalTestViewSet: modified get_queryset, destroy, list, and bulk_upsert methods for improved logic and validation.
care/facility/api/viewsets/patient_otp_data.py Adjusted get_queryset in OTPPatientDataViewSet to use super().get_queryset().
care/facility/api/viewsets/shifting.py Updated ShiftingViewSet and ShiftingRequestCommentViewSet: modified get_queryset and get_serializer_class.
care/users/api/viewsets/users.py Modified UserViewSet: updated get_queryset, destroy, add_facility, and get_object methods for better permission handling and error messages.

Poem

In the code where queries flow,
Super calls now steal the show.
Permissions tightened, logic clear,
CSVs handled, no need to fear.
With every change, we dance and cheer,
For cleaner code is finally here! 🎉


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

codecov bot commented Nov 5, 2024

Codecov Report

Attention: Patch coverage is 73.33333% with 4 lines in your changes missing coverage. Please review.

Project coverage is 69.20%. Comparing base (5cba13c) to head (1e9d537).
Report is 1 commits behind head on develop.

Files with missing lines Patch % Lines
care/facility/api/viewsets/patient_consultation.py 50.00% 3 Missing ⚠️
care/facility/api/viewsets/patient_otp_data.py 0.00% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##           develop    #2582   +/-   ##
========================================
  Coverage    69.20%   69.20%           
========================================
  Files          211      211           
  Lines        11942    11944    +2     
  Branches      1208     1208           
========================================
+ Hits          8264     8266    +2     
  Misses        3302     3302           
  Partials       376      376           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link

@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: 0

🧹 Outside diff range and nitpick comments (8)
care/facility/api/viewsets/patient_otp_data.py (1)

Line range hint 21-26: Yo, solid upgrade on that queryset fetch! 🚀

Using super().get_queryset() instead of directly accessing self.queryset is totally the way to go! It ensures you're always getting fresh data instead of potentially stale cached results. Keep that data flow clean and crispy!

Pro tip: You might want to add a quick docstring to explain the OTP filtering logic, something chill like:

 def get_queryset(self):
+    """
+    Returns patient data filtered by phone number for OTP users,
+    empty queryset for everyone else. Keeps it exclusive, you know what I mean? 😎
+    """
     is_otp_login = getattr(self.request.user, "is_alternative_login", False)
care/facility/api/viewsets/mixins/access.py (1)

Line range hint 23-39: Hey fam, heads up about some filtering inconsistency! 🤔

I noticed filter_by_user_scope and get_queryset are like twins but not identical - they're doing similar things but checking for slightly different attributes:

  • get_queryset checks for district
  • filter_by_user_scope checks for district_id

This could get confusing real quick! Maybe we should make these consistent?

Here's a chill way to refactor this:

     def get_queryset(self):
         queryset = super().get_queryset()
         model = self.queryset.__dict__["model"]

         if not self.request.user.is_superuser:
             instance = model()
             if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictAdmin"]:
-                if hasattr(instance, "district"):
+                if hasattr(instance, "district_id"):
                     queryset = queryset.filter(district=self.request.user.district)
-                if hasattr(instance, "facility"):
+                if hasattr(instance, "facility_id"):
                     queryset = queryset.filter(
                         facility__district=self.request.user.district
                     )

This way, both methods will be checking for the same attributes. Much cleaner! 🧹

care/facility/api/viewsets/patient_external_test.py (2)

Line range hint 134-137: Hey, let's make this permission check a bit more user-friendly! 🤙

The check is working fine, but we could make it more helpful for the users who get denied.

Here's a chill way to improve it:

     def destroy(self, request, *args, **kwargs):
         if self.request.user.user_type < User.TYPE_VALUE_MAP["DistrictLabAdmin"]:
-            raise PermissionDenied
+            raise PermissionDenied("Sorry fam, only District Lab Admins and above can delete test records! 🛑")
         return super().destroy(request, *args, **kwargs)

Line range hint 154-189: Digging the validation vibes, but we can make it even smoother! 🌊

The district validation is crucial, but we could make this whole flow more efficient and readable.

Here's a more streamlined approach:

     @action(methods=["POST"], detail=False)
     def bulk_upsert(self, request, *args, **kwargs):
         if not self.check_upload_permission():
-            msg = "Permission to Endpoint Denied"
-            raise PermissionDenied(msg)
+            raise PermissionDenied("No access to this endpoint, my friend! 🚫")
         if "sample_tests" not in request.data:
             raise ValidationError({"sample_tests": "No Data was provided"})
         if not isinstance(request.data["sample_tests"], list):
             raise ValidationError({"sample_tests": "Data should be provided as a list"})
 
-        # check if the user is from same district
-        for data in request.data["sample_tests"]:
-            if str(request.user.district) != data["district"]:
-                raise ValidationError({"Error": "User must belong to same district"})
+        # Validate all districts match in one go
+        if any(str(request.user.district) != data["district"] for data in request.data["sample_tests"]):
+            raise ValidationError({"Error": "All samples must be from your district! 🗺️"})
 
         errors = []
-        ser_objects = []
-        invalid = False
-        for sample in request.data["sample_tests"]:
-            serializer = PatientExternalTestSerializer(data=sample)
-            valid = serializer.is_valid()
-            current_error = pretty_errors(serializer._errors)  # noqa: SLF001
-            if current_error and (not valid):
-                errors.append(current_error)
-                invalid = True
-            ser_objects.append(serializer)
-        if invalid:
+        serializers = [PatientExternalTestSerializer(data=sample) for sample in request.data["sample_tests"]]
+        
+        if not all(serializer.is_valid() for serializer in serializers):
+            errors = [pretty_errors(s._errors) for s in serializers if not s.is_valid()]  # noqa: SLF001
             return Response(errors, status=status.HTTP_400_BAD_REQUEST)
-        for ser_object in ser_objects:
-            ser_object.save()
+        
+        for serializer in serializers:
+            serializer.save()
         return Response(status=status.HTTP_202_ACCEPTED)

This refactor:

  • Makes district validation more efficient with a single pass
  • Simplifies serializer handling
  • Keeps the same functionality but with cleaner code

Consider using bulk_create for better performance if you're dealing with large datasets!

care/facility/api/viewsets/shifting.py (2)

Line range hint 112-152: Yo, nice queryset optimization! 🚀

The changes look solid! Using super().get_queryset() is totally the way to go for fresh results. Love how you split the select_related fields between list and detail views - that's some performance-conscious coding right there!

Just one chill suggestion - maybe we could extract those select_related fields into class constants? Would make it easier to maintain those long lists.

class ShiftingViewSet:
    LIST_SELECT_RELATED = [
        "origin_facility",
        "shifting_approving_facility",
        "assigned_facility",
        "patient",
    ]
    
    DETAIL_SELECT_RELATED = [
        "origin_facility",
        "origin_facility__ward",
        # ... rest of the fields
    ]

Line range hint 249-285: Hey, let's keep the fresh queryset vibe going! 🌊

While we're updating queryset patterns, might wanna give this method the same treatment. Adding super().get_queryset() here would keep things consistent with the changes above.

    def get_queryset(self):
-       queryset = self.queryset.filter(
+       queryset = super().get_queryset().filter(
            request__external_id=self.kwargs.get("shift_external_id")
        )
care/facility/api/viewsets/patient_consultation.py (1)

Line range hint 85-113: Yo, nice refactor on the get_queryset method! 🎯

The changes look solid! Using super().get_queryset() and a local variable is totally the way to go for avoiding those pesky cached results. The permission checks and filters are still doing their thing just right.

Just one tiny suggestion to make it even more awesome - consider extracting those complex permission filters into a separate method for better readability. Something like this would be super clean:

 def get_queryset(self):
     queryset = super().get_queryset()
     if self.serializer_class == PatientConsultationSerializer:
         queryset = queryset.prefetch_related(...)
     if self.request.user.is_superuser:
         return queryset
     if self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]:
         return queryset.filter(patient__facility__state=self.request.user.state)
     if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]:
         return queryset.filter(
             patient__facility__district=self.request.user.district
         )
+    return self._apply_facility_filters(queryset)
+
+def _apply_facility_filters(self, queryset):
     allowed_facilities = get_accessible_facilities(self.request.user)
     applied_filters = Q(
         Q(patient__is_active=True) & Q(patient__facility__id__in=allowed_facilities)
     )
     applied_filters |= Q(facility=self.request.user.home_facility)
     return queryset.filter(applied_filters)

But no pressure, the current implementation is totally working great too! 🚀

care/users/api/viewsets/users.py (1)

181-181: Nice security move, but we can make it even smoother! 🔒

Using self.get_queryset() is totally the right call for keeping those permissions tight. But check this out - we could make it a bit more efficient.

Instead of getting the queryset and then filtering it again with username, we could do it all in one go:

-        queryset = self.get_queryset()
-        username = kwargs["username"]
-        if request.user.is_superuser:
+        username = kwargs["username"]
+        if request.user.is_superuser:
+            queryset = self.get_queryset()

Just a small optimization to keep things running smooth! 🏃‍♂️

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 5cba13c and 1e9d537.

📒 Files selected for processing (6)
  • care/facility/api/viewsets/mixins/access.py (1 hunks)
  • care/facility/api/viewsets/patient_consultation.py (3 hunks)
  • care/facility/api/viewsets/patient_external_test.py (1 hunks)
  • care/facility/api/viewsets/patient_otp_data.py (1 hunks)
  • care/facility/api/viewsets/shifting.py (2 hunks)
  • care/users/api/viewsets/users.py (2 hunks)
🔇 Additional comments (5)
care/facility/api/viewsets/mixins/access.py (2)

8-8: Yo, solid move using super().get_queryset()! 🎯

This change is totally on point - it ensures we're always getting fresh data instead of potentially stale cached results. Perfect alignment with the PR's goals!


Line range hint 47-60: Quick security check on the asset auth flow! 🔒

The authentication setup looks solid, but let's make sure we're not accidentally skipping any important checks in the authentication chain.

✅ Verification successful

Let me check the authentication chain more thoroughly before making a final call.


Yo, the auth flow is totally solid! 🔒✌️

The security setup is actually pretty tight:

  • Asset authentication is properly implemented with JWT validation and explicit asset_id checks
  • DRYAssetPermissions adds an extra layer by prefixing asset-specific permissions
  • Asset users are properly restricted from write operations in multiple places
  • No sketchy direct asset assignments found outside of test files

Keep rockin' that secure code! 🎸

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the authentication chain and usage
# Look for any bypass of asset authentication or direct asset access

# Check for proper asset authentication usage
echo "Checking for proper asset authentication usage..."
rg -l "MiddlewareAssetAuthentication" 

# Check for potential security bypasses
echo "Checking for potential security bypasses..."
rg "request.user.asset" -C 3

Length of output: 6596


Script:

#!/bin/bash
# Let's check the authentication implementation and permission classes

# Check the middleware asset authentication implementation
echo "Checking MiddlewareAssetAuthentication implementation..."
rg -A 10 "class MiddlewareAssetAuthentication" 

# Check DRYAssetPermissions implementation
echo "Checking DRYAssetPermissions implementation..."
rg -A 10 "class DRYAssetPermissions"

# Check if there are any direct asset assignments or modifications
echo "Checking asset assignments..."
rg "\.asset\s*=" -C 3

Length of output: 11213

care/facility/api/viewsets/patient_external_test.py (2)

Line range hint 98-112: Yo, this queryset change is solid! 🎯

Switching to super().get_queryset() is totally the way to go - keeps things fresh and avoids those pesky cached results. The permission filtering is still on point too!


Line range hint 142-152: CSV handling looking clean! ✨

The CSV export logic is neat, but let's make sure those mapping keys are all good.

Let's double-check those CSV mappings real quick:

care/users/api/viewsets/users.py (1)

128-128: Yo, this change is solid! 🎯

Switching to super().get_queryset() is a sweet move - keeps things fresh and inherits all the good stuff from the parent class. No cached surprises here!

@vigneshhari vigneshhari merged commit d6d069e into develop Nov 6, 2024
8 checks passed
@vigneshhari vigneshhari deleted the sainak/fix/queryset-caching branch November 6, 2024 06:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

2 participants