Skip to content

Conversation

@praffq
Copy link
Contributor

@praffq praffq commented May 15, 2025

Proposed Changes

  • Added viewset for facility and user flag , only accessible to super user

Associated Issue

Architecture changes

  • Remove this section if not used

Merge Checklist

  • Tests added/fixed
  • Update docs in /docs
  • Linting Complete
  • Any other necessary step

Only PR's with test cases included and passing lint and test pipelines will be reviewed

@ohcnetwork/care-backend-maintainers @ohcnetwork/care-backend-admins

Summary by CodeRabbit

  • New Features

    • Added facility- and user-scoped feature flag management with CRUD APIs and endpoints to list available flags; restricted to superusers.
    • Flags are registered/unregistered centrally and include cache-backed lookups for faster reads.
  • Bug Fixes

    • Validation prevents creating duplicate flags for the same facility or user; safe cleanup unregisters unused flags after deletions.
  • Chores

    • Consolidated flag models and logic into the EMR module and updated model references and migrations for consistency.

✏️ Tip: You can customize this high-level summary in your review settings.

@praffq praffq requested a review from a team as a code owner May 15, 2025 18:03
@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 15, 2025

📝 Walkthrough

Walkthrough

Moved Feature Flag models and logic into the emr app: added FacilityFlag and UserFlag models, a BaseFlag scaffold with caching, new viewsets and Pydantic specs for flag management, router registrations, migrations deleting old models, and adjusted imports referencing the new locations.

Changes

Cohort / File(s) Change Summary
API viewsets
care/emr/api/viewsets/facility.py, care/emr/api/viewsets/user.py
Added FacilityFlagViewSet and UserFlagViewSet, filters, superuser-only permission checks, create/destroy hooks to register/unregister flags with FlagRegistry, cache invalidation, and available-flags list endpoints.
Models — emr app
care/emr/models/base.py, care/emr/models/user.py, care/emr/models/facility.py, care/emr/models/file_upload.py, care/emr/models/patient.py, care/emr/models/scheduling/booking.py
Introduced abstract BaseFlag with caching and helper methods; added UserFlag model; adjusted FacilityFlag internals (import/constraint/name) and switched several User FK references to string "users.User".
Migrations — emr app
care/emr/migrations/0030_facilityflag_userflag.py
Added migration creating FacilityFlag and UserFlag models in emr.
Resources / specs
care/emr/resources/facility/spec.py, care/emr/resources/user/spec.py
Added Pydantic specs: FacilityFlagBase/Create/Read and UserFlagBase/Create/Read with validation, deserialization, and serialization logic.
Removed from old apps / deletions
care/facility/migrations/0477_delete_facilityflag.py, care/users/migrations/0024_delete_userflag.py, care/facility/models/__init__.py, care/facility/models/facility.py, care/users/models.py, care/users/admin.py
Deleted old FacilityFlag and UserFlag models from facility/users apps, removed auto-import of facility_flag, updated imports to point to emr locations.
Removed legacy base
care/utils/models/base.py
Removed previous BaseFlag implementation and related flag caching/constants.
Router
config/api_router.py
Registered facility_flags and user_flags viewsets and added imports.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • Transactional create/destroy hooks that interact with FlagRegistry and post-commit unregister logic.
    • Cache key templates and TTL usages in new BaseFlag vs. removed legacy implementation.
    • Migrations: ensure model movement/drops align with deployment order to avoid downtime.
    • Specs’ validation/deserialization paths that resolve related objects by external_id/UUID.

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description follows the template with proposed changes and associated issue, but critical merge checklist items remain unchecked despite the repository requirement that tests and linting must pass. Check the boxes for completed items or confirm that tests have been added/fixed and linting has passed before merge; update docs as needed.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the primary change: introduction of viewsets for managing user and facility flags with superuser-only access.
✨ 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 prafful/feat/user-and-facility-flag

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

Copy link
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: 5

🧹 Nitpick comments (9)
care/users/migrations/0024_delete_userflag.py (1)

1-16: Migration looks good but could use quotes consistency

The migration correctly removes the UserFlag model from the users app as part of the EMR app refactoring. I suppose someone could have made the migration use double quotes instead of single quotes to match the project style, but I guess it works fine as is.

-        ('users', '0023_user_prefix_user_suffix'),
+        ("users", "0023_user_prefix_user_suffix"),
-            name='UserFlag',
+            name="UserFlag",
🧰 Tools
🪛 Ruff (0.8.2)

9-9: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


9-9: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


14-14: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)

config/api_router.py (1)

278-280: Added router registrations for flag viewsets

The new router registrations for facility_flags and user_flags endpoints are properly configured. The trailing empty line maintains the code structure formatting.

It would be helpful to have a comment indicating that these endpoints are restricted to superusers, since that's an important security detail mentioned in the PR objectives.

 router.register(r"facility_flags", FacilityFlagViewSet, basename="facility-flags")
 router.register(r"user_flags", UserFlagViewSet, basename="user-flags")
+# Note: Both flag endpoints above are restricted to superusers only
care/emr/models/user.py (3)

23-28: Consider adding a more descriptive name for the UniqueConstraint

While the constraint itself is correct, the constraint name "emr_unique_user_flag" could be more descriptive to better indicate what uniqueness is being enforced. Maybe something like "emr_user_flag_unique_combination" would be slightly clearer for future developers.

models.UniqueConstraint(
    fields=["user", "flag"],
    condition=models.Q(deleted=False),
-   name="emr_unique_user_flag",
+   name="emr_user_flag_unique_combination",
)

30-33: Consider adding docstring to the check_user_has_flag method

The method signature is clear, but adding a brief docstring would improve code readability and provide context for future developers about the method's purpose and expected behavior.

@classmethod
def check_user_has_flag(cls, user_id: int, flag_name: FlagName) -> bool:
+   """
+   Check if a user has a specific flag.
+   
+   Args:
+       user_id: The ID of the user to check
+       flag_name: The name of the flag to check for
+       
+   Returns:
+       True if the user has the flag, False otherwise
+   """
    return cls.check_entity_has_flag(user_id, flag_name)

35-36: Consider adding docstring to the get_all_flags method

Similar to the previous method, adding a brief docstring would improve code readability and documentation.

@classmethod
def get_all_flags(cls, user_id: int) -> tuple[FlagName]:
+   """
+   Get all flags associated with a user.
+   
+   Args:
+       user_id: The ID of the user to retrieve flags for
+       
+   Returns:
+       A tuple of FlagName values associated with the user
+   """
    return super().get_all_flags(user_id)
care/emr/models/base.py (1)

64-77: check_entity_has_flag will cache negative look-ups forever

With cache.get_or_set the lambda is evaluated once and its result (True/False) is stored for a whole day.
If a flag is subsequently added, consumers calling check_entity_has_flag will still receive the old False value until the TTL expires.

Consider:

  1. Bumping the TTL down, or
  2. Explicitly invalidating this key in the concrete model’s save/delete methods (you already clear it there – good), and
  3. Adding a short comment explaining this expiry strategy.
care/emr/resources/facility/spec.py (1)

76-79: Small nomenclature slip

__exclude__ = ["facility"] removes the FK from the serialized output, yet FacilityFlagReadSpec manually re-injects a nested facility dict. That is fine, but a brief comment here would save the next reader a head-scratch.

care/emr/migrations/0030_facilityflag_userflag.py (1)

28-36: Consider adding an index on flag for faster look-ups.

Both FacilityFlag and UserFlag are queried by flag (see the unique constraint and the new viewsets’ filters).
Adding db_index=True on flag will prevent full-table scans once the tables grow.

- ('flag', models.CharField(max_length=1024)),
+ ('flag', models.CharField(max_length=1024, db_index=True)),

A one-line migration change now will save you a painful data-migration later.

🧰 Tools
🪛 Ruff (0.8.2)

28-28: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


29-29: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


29-29: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


30-30: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


30-30: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


31-31: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


31-31: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


34-34: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


34-34: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


35-35: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


35-35: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


35-35: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


35-35: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


35-35: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)

care/emr/resources/user/spec.py (1)

202-205: id should be serialised as a string for consistency.

Elsewhere (UserRetrieveSpec, etc.) external_id is stringified; returning a raw UUID here produces mixed types in API responses.

-        mapping["id"] = obj.external_id
+        mapping["id"] = str(obj.external_id)

A tiny tweak that spares consumers an awkward type dance.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1fa63f2 and 323930a.

📒 Files selected for processing (20)
  • care/emr/api/viewsets/facility.py (3 hunks)
  • care/emr/api/viewsets/user.py (3 hunks)
  • care/emr/migrations/0030_facilityflag_userflag.py (1 hunks)
  • care/emr/models/base.py (2 hunks)
  • care/emr/models/facility.py (2 hunks)
  • care/emr/models/file_upload.py (1 hunks)
  • care/emr/models/patient.py (1 hunks)
  • care/emr/models/scheduling/booking.py (1 hunks)
  • care/emr/models/user.py (1 hunks)
  • care/emr/resources/facility/spec.py (3 hunks)
  • care/emr/resources/user/spec.py (3 hunks)
  • care/facility/migrations/0477_delete_facilityflag.py (1 hunks)
  • care/facility/models/__init__.py (0 hunks)
  • care/facility/models/facility.py (1 hunks)
  • care/users/admin.py (1 hunks)
  • care/users/migrations/0024_delete_userflag.py (1 hunks)
  • care/users/models.py (1 hunks)
  • care/utils/models/base.py (0 hunks)
  • care/utils/registries/feature_flag.py (1 hunks)
  • config/api_router.py (3 hunks)
💤 Files with no reviewable changes (2)
  • care/facility/models/init.py
  • care/utils/models/base.py
🧰 Additional context used
🧠 Learnings (2)
care/facility/models/facility.py (1)
Learnt from: DraKen0009
PR: ohcnetwork/care#2620
File: care/facility/models/facility.py:306-311
Timestamp: 2024-11-28T06:16:31.373Z
Learning: In the 'care' project, moving imports of models like `Asset`, `AssetLocation`, `FacilityDefaultAssetLocation`, and `PatientSample` to the module level in `care/facility/models/facility.py` causes circular imports. Therefore, it's acceptable to keep these imports inside the `delete` method of the `Facility` class.
care/emr/api/viewsets/facility.py (1)
Learnt from: aravindm4
PR: ohcnetwork/care#2585
File: care/facility/api/viewsets/facility_flag.py:14-27
Timestamp: 2024-11-26T05:41:40.226Z
Learning: In the `care/facility/api/viewsets/facility_flag.py` file and similar viewsets in this Django REST Framework project, pagination is added by default. For APIs restricted to superusers using the `IsSuperUser` permission class, additional rate limiting or pagination adjustments are not required.
🧬 Code Graph Analysis (7)
care/facility/models/facility.py (1)
care/emr/models/facility.py (1)
  • FacilityFlag (7-36)
care/facility/migrations/0477_delete_facilityflag.py (2)
care/emr/migrations/0030_facilityflag_userflag.py (1)
  • Migration (9-58)
care/users/migrations/0024_delete_userflag.py (1)
  • Migration (6-16)
care/users/models.py (2)
care/emr/models/user.py (1)
  • UserFlag (7-36)
care/utils/models/base.py (1)
  • BaseModel (12-29)
care/users/admin.py (1)
care/emr/models/user.py (1)
  • UserFlag (7-36)
care/emr/models/facility.py (1)
care/emr/models/base.py (1)
  • BaseFlag (35-89)
care/emr/resources/facility/spec.py (6)
care/emr/models/facility.py (2)
  • FacilityFlag (7-36)
  • check_facility_has_flag (31-32)
care/utils/registries/feature_flag.py (1)
  • FlagNotFoundError (9-10)
care/emr/resources/base.py (3)
  • EMRResource (10-152)
  • is_update (41-42)
  • to_json (142-143)
care/emr/resources/user/spec.py (8)
  • validate_flag (184-190)
  • perform_extra_deserialization (66-70)
  • perform_extra_deserialization (118-119)
  • perform_extra_deserialization (192-194)
  • perform_extra_serialization (133-136)
  • perform_extra_serialization (146-156)
  • perform_extra_serialization (167-169)
  • perform_extra_serialization (202-205)
care/emr/resources/organization/spec.py (2)
  • perform_extra_deserialization (45-50)
  • perform_extra_serialization (60-62)
care/facility/models/facility.py (1)
  • Facility (233-409)
care/emr/migrations/0030_facilityflag_userflag.py (2)
care/facility/migrations/0477_delete_facilityflag.py (1)
  • Migration (6-16)
care/users/migrations/0024_delete_userflag.py (1)
  • Migration (6-16)
🪛 Ruff (0.8.2)
care/users/migrations/0024_delete_userflag.py

9-9: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


9-9: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


14-14: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)

care/facility/migrations/0477_delete_facilityflag.py

9-9: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


9-9: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


14-14: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)

care/emr/migrations/0030_facilityflag_userflag.py

12-12: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


12-12: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


13-13: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


13-13: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


19-19: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


21-21: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


21-21: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


22-22: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


23-23: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


24-24: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


25-25: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


26-26: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


27-27: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


28-28: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


29-29: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


29-29: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


30-30: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


30-30: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


31-31: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


31-31: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


34-34: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


34-34: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


35-35: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


35-35: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


35-35: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


35-35: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


35-35: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


39-39: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


41-41: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


41-41: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


42-42: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


43-43: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


44-44: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


45-45: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


46-46: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


47-47: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


48-48: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


49-49: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


49-49: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


50-50: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


50-50: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


51-51: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


54-54: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


54-54: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


55-55: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


55-55: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


55-55: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


55-55: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


55-55: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Test / Test
  • GitHub Check: Analyze (python)
🔇 Additional comments (16)
care/users/admin.py (1)

7-7: Import update looks reasonable.

The import has been updated to fetch UserFlag from care.emr.models.user as part of the model migration to the EMR app. This change aligns properly with the refactoring described in the PR objectives.

care/emr/models/facility.py (2)

3-3: Updated import path is appropriate.

The import path for BaseFlag has been updated to reflect its relocation to the EMR app, which is consistent with the overall refactoring goal.


26-26:

Details

✅ Verification successful

Constraint rename makes sense, but ensure there's a migration.

The unique constraint name has been updated with an "emr_" prefix, which helps avoid naming conflicts after moving the model to the EMR app. Just make sure this change is covered by a migration file.


🏁 Script executed:

#!/bin/bash
# Check if there's a migration file that updates the constraint name
rg -A 5 -B 5 "emr_unique_facility_flag" --type py care/emr/migrations/

Length of output: 1306


Migration confirmed: emr_unique_facility_flag is implemented

  • care/emr/migrations/0030_facilityflag_userflag.py defines the UniqueConstraint(name='emr_unique_facility_flag').
    Seems you were one step ahead—no further migration required.
care/facility/models/facility.py (1)

13-13: Import path correctly updated.

The import path for FacilityFlag now points to the model's new location in care.emr.models.facility rather than its previous location, which maintains consistency with the model migration.

care/utils/registries/feature_flag.py (1)

13-13: Good enhancement to the FlagType enum.

Making FlagType inherit from str in addition to enum.Enum simplifies usage by allowing enum values to be used directly in string contexts without explicit conversion. This is particularly useful when passing flag types to Django ORM queries or template rendering.

care/facility/migrations/0477_delete_facilityflag.py (1)

1-16: LGTM: Migration deletes FacilityFlag model as part of EMR app refactoring

This migration correctly removes the FacilityFlag model from the facility app as part of moving it to the EMR app. The dependency on the previous migration is properly specified.

🧰 Tools
🪛 Ruff (0.8.2)

9-9: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


9-9: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)


14-14: Single quotes found but double quotes preferred

Replace single quotes with double quotes

(Q000)

care/emr/models/file_upload.py (1)

24-30: LGTM: String reference to User model prevents circular imports

The change from direct import to string reference ("users.User") is a good practice to avoid circular dependencies. This aligns with similar changes made in other models.

config/api_router.py (2)

23-29: Added FacilityFlagViewSet import

The import of FacilityFlagViewSet is properly placed within the facility viewsets group.


74-74: Updated UserViewSet import to include UserFlagViewSet

The import now includes UserFlagViewSet alongside UserViewSet, which is clean and organized.

care/emr/models/patient.py (1)

117-117: Good update to use the string reference for the User model

This change improves code organization by using Django's string-based lazy reference "users.User" instead of a direct import. This helps prevent circular dependencies and aligns with the refactoring pattern seen across other files.

care/users/models.py (2)

13-14: Nice relocation of the UserFlag model to the EMR app

The import change properly reflects the architectural decision to move flag-related models from the users app to the EMR app. This should make the codebase slightly more organized, though it would have been even better with a comment explaining the reasoning behind this migration.


425-425: LGTM - Method usage consistent with model relocation

The get_all_flags method correctly uses the imported UserFlag class, ensuring functionality is maintained despite the model being relocated to the EMR app.

care/emr/models/scheduling/booking.py (1)

31-33: Good update to use string reference for User model

This change properly updates the booked_by field to use a string-based lazy reference ("users.User") instead of direct import, which is consistent with the changes in other models and helps prevent circular dependencies.

care/emr/models/user.py (1)

7-36: Well-structured UserFlag model implementation

The UserFlag model is well-implemented with appropriate ForeignKey relationship, cache key templates, and constraint definitions. The class methods provide a clean interface for flag checking and retrieval.

care/emr/api/viewsets/user.py (1)

189-191: permissions_controller API contract is unclear

permissions_controller currently returns a bare boolean.
Double-check that EMRBaseViewSet interprets this correctly; many DRF permission hooks expect either a list/tuple of permission classes or an object with a has_permission method. A silent False here could inadvertently expose or block the endpoint.

care/emr/api/viewsets/facility.py (1)

214-221: ⚠️ Potential issue

Wrong flag type passed to registry – returns user flags instead of facility flags.

list_available_flags lives in FacilityFlagViewSet, yet asks for FlagType.USER, so the endpoint will list user flags, not facility flags, and may even 404 if none exist.

-            flags = FlagRegistry.get_all_flags(FlagType.USER)
+            flags = FlagRegistry.get_all_flags(FlagType.FACILITY)

Please also fix the doc-string (“UserFlag”) to avoid future head-scratching.

Likely an incorrect or invalid review comment.

@codecov
Copy link

codecov bot commented May 15, 2025

Codecov Report

Attention: Patch coverage is 63.54167% with 70 lines in your changes missing coverage. Please review.

Project coverage is 51.56%. Comparing base (1fa63f2) to head (7597a7f).

Files with missing lines Patch % Lines
care/emr/api/viewsets/facility.py 54.28% 16 Missing ⚠️
care/emr/api/viewsets/user.py 52.94% 16 Missing ⚠️
care/emr/models/base.py 66.66% 12 Missing ⚠️
care/emr/resources/user/spec.py 60.00% 12 Missing ⚠️
care/emr/resources/facility/spec.py 63.33% 11 Missing ⚠️
care/emr/models/user.py 85.00% 3 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #3044      +/-   ##
===========================================
+ Coverage    51.40%   51.56%   +0.16%     
===========================================
  Files          248      250       +2     
  Lines        11157    11323     +166     
  Branches      1241     1247       +6     
===========================================
+ Hits          5735     5839     +104     
- Misses        5403     5465      +62     
  Partials        19       19              

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

🧹 Nitpick comments (6)
care/emr/models/base.py (6)

32-32: Constants should follow Python naming conventions more consistently.

The constant is defined correctly, but it might be better to use SNAKE_CASE for all constants. The one-day TTL seems reasonable, but I suppose you've really thought through whether this is the optimal duration for your use case.

-FLAGS_CACHE_TTL = 60 * 60 * 24  # 1 Day
+FLAG_CACHE_TTL = 60 * 60 * 24  # 1 Day

35-42: Class variables could use more explicit documentation.

The BaseFlag class is well structured, but the empty/None class variables without docstrings might confuse future developers. It would be helpful to add docstrings explaining that these should be overridden by subclasses.

 class BaseFlag(EMRBaseModel):
     flag = models.CharField(max_length=1024)
 
+    # Override these in subclasses
     cache_key_template = ""
     all_flags_cache_key_template = ""
     flag_type = None
     entity_field_name = ""

64-67: Flag validation is well-centralized, but lacks documentation.

The validation method is concise and correctly leverages the FlagRegistry. A docstring would make its purpose clearer to other developers. Also, consider adding return type hints.

 @classmethod
-def validate_flag(cls, flag_name: FlagName):
+def validate_flag(cls, flag_name: FlagName) -> None:
+    """
+    Validates that the given flag name is registered for this flag type.
+    
+    Args:
+        flag_name: The flag name to validate
+        
+    Raises:
+        ValueError: If the flag name is not registered for this flag type
+    """
     FlagRegistry.validate_flag_name(cls.flag_type, flag_name)

68-78: Cache implementation for flag checking is efficient but could be more robust.

The method efficiently uses get_or_set for caching, but it doesn't handle potential errors from cache operations. Also, it might be better to use keyword arguments for the lambda to improve readability.

 @classmethod
 def check_entity_has_flag(cls, entity_id: int, flag_name: FlagName) -> bool:
     cls.validate_flag(flag_name)
-    return cache.get_or_set(
-        cls.cache_key_template.format(entity_id=entity_id, flag_name=flag_name),
-        default=lambda: cls.objects.filter(
-            **{f"{cls.entity_field_name}_id": entity_id, "flag": flag_name}
-        ).exists(),
-        timeout=FLAGS_CACHE_TTL,
-    )
+    cache_key = cls.cache_key_template.format(entity_id=entity_id, flag_name=flag_name)
+    try:
+        return cache.get_or_set(
+            cache_key,
+            default=lambda: cls.objects.filter(
+                **{f"{cls.entity_field_name}_id": entity_id, "flag": flag_name}
+            ).exists(),
+            timeout=FLAGS_CACHE_TTL,
+        )
+    except Exception as e:
+        # Log the error but continue to fetch from database directly
+        # logger.error(f"Cache error in check_entity_has_flag: {e}")
+        return cls.objects.filter(
+            **{f"{cls.entity_field_name}_id": entity_id, "flag": flag_name}
+        ).exists()

79-90: The get_all_flags method returns a tuple, which might be unexpected.

Converting the QuerySet values to a tuple is immutable and efficient, but this should be documented as it might surprise callers expecting a list or QuerySet. Also, the return type hint could be more specific using tuple[str, ...].

 @classmethod
-def get_all_flags(cls, entity_id: int) -> tuple[FlagName]:
+def get_all_flags(cls, entity_id: int) -> tuple[FlagName, ...]:
+    """
+    Returns all flags for the given entity as an immutable tuple.
+    
+    Args:
+        entity_id: The ID of the entity
+        
+    Returns:
+        An immutable tuple of flag names
+    """
     return cache.get_or_set(
         cls.all_flags_cache_key_template.format(entity_id=entity_id),
         default=lambda: tuple(

91-96: The invalidate_cache method could check for empty strings.

It's possible that a subclass might not override the cache key templates, which could lead to unexpected behavior. Adding a check would make this method more robust.

 @classmethod
 def invalidate_cache(cls, entity_id: int, flag_name: str):
+    if not cls.cache_key_template or not cls.all_flags_cache_key_template:
+        # logger.warning(f"{cls.__name__} has empty cache key templates")
+        return
+
     cache.delete(
         cls.cache_key_template.format(entity_id=entity_id, flag_name=flag_name)
     )
     cache.delete(cls.all_flags_cache_key_template.format(entity_id=entity_id))
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 323930a and 7597a7f.

📒 Files selected for processing (7)
  • care/emr/api/viewsets/facility.py (4 hunks)
  • care/emr/api/viewsets/user.py (3 hunks)
  • care/emr/models/base.py (2 hunks)
  • care/emr/models/facility.py (3 hunks)
  • care/emr/models/user.py (1 hunks)
  • care/emr/resources/facility/spec.py (3 hunks)
  • care/emr/resources/user/spec.py (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • care/emr/models/facility.py
  • care/emr/models/user.py
  • care/emr/api/viewsets/user.py
  • care/emr/resources/facility/spec.py
  • care/emr/resources/user/spec.py
  • care/emr/api/viewsets/facility.py
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Test / Test
🔇 Additional comments (3)
care/emr/models/base.py (3)

1-5: Good to see proper imports for the caching functionality.

The import of Django's cache module and the FlagRegistry components are appropriate for implementing the caching strategy. It would have been nice to see some comments explaining the purpose of these imports though.


56-62: Properties efficiently provide access to the entity and its ID.

These dynamic properties are a clean approach to access the related entity based on the subclass configuration. The code assumes standard Django foreign key naming conventions, which seems reasonable.


46-54:

Details

❌ Incorrect review comment

The save method doesn't handle bulk operations.

The current implementation validates and clears cache correctly for individual saves, but won't work with bulk_create or bulk_update. Consider implementing model signals or custom manager methods to handle bulk operations properly.


🏁 Script executed:

#!/bin/bash
# Look for any bulk operations on flag models
rg --type py "bulk_(create|update)" -g "care/emr/**"

Length of output: 398


No bulk operations on flag models—suggestion not applicable

I didn’t find any bulk_create or bulk_update calls on any models inheriting from care/emr/models/base.py, so the overridden save is always invoked. You can safely ignore this recommendation.

  • care/emr/models/base.py (save method, lines 46–54)

Likely an incorrect or invalid review comment.

Copy link
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: 5

🧹 Nitpick comments (4)
care/emr/models/patient.py (1)

145-153: Use settings.AUTH_USER_MODEL for consistency with codebase conventions.

The lazy string reference ForeignKey("users.User", ...) is functionally correct and improves import decoupling. However, the codebase inconsistently references the User model: migrations extensively use settings.AUTH_USER_MODEL, while these four model FK fields (in patient.py, valueset.py, and organization.py) hard-code the string. For consistency and future-proofing, consider aligning PatientUser.user to use settings.AUTH_USER_MODEL instead.

care/emr/models/base.py (2)

70-79: Consider adding explicit deleted=False filter.

The cache lookup doesn't explicitly filter out soft-deleted flags. While the unique constraint uses condition=models.Q(deleted=False), the cache query should also filter by deleted=False to avoid returning stale flags.

     @classmethod
     def check_entity_has_flag(cls, entity_id: int, flag_name: FlagName) -> bool:
         cls.validate_flag(flag_name)
         return cache.get_or_set(
             cls.cache_key_template.format(entity_id=entity_id, flag_name=flag_name),
             default=lambda: cls.objects.filter(
-                **{f"{cls.entity_field_name}_id": entity_id, "flag": flag_name}
+                **{f"{cls.entity_field_name}_id": entity_id, "flag": flag_name, "deleted": False}
             ).exists(),
             timeout=FLAGS_CACHE_TTL,
         )

81-91: Soft-deleted flags may be included in results.

Similar to the issue above, get_all_flags doesn't explicitly filter by deleted=False, which could return soft-deleted flags from the database when cache is cold.

     @classmethod
     def get_all_flags(cls, entity_id: int) -> tuple[FlagName]:
         return cache.get_or_set(
             cls.all_flags_cache_key_template.format(entity_id=entity_id),
             default=lambda: tuple(
                 cls.objects.filter(
-                    **{f"{cls.entity_field_name}_id": entity_id}
+                    **{f"{cls.entity_field_name}_id": entity_id, "deleted": False}
                 ).values_list("flag", flat=True)
             ),
             timeout=FLAGS_CACHE_TTL,
         )
care/emr/api/viewsets/user.py (1)

238-246: Consider adding explicit deleted=False filter for clarity.

While the logic works, explicitly filtering by deleted=False would make the intent clearer and match the pattern used in the facility viewset.

     def _safe_unregister_flag_if_unused(self, flag_name: str, deleted_instance_id: int):
         still_used = (
-            UserFlag.objects.filter(flag=flag_name)
+            UserFlag.objects.filter(flag=flag_name, deleted=False)
             .exclude(id=deleted_instance_id)
             .exists()
         )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f5f56d6 and 5692089.

📒 Files selected for processing (7)
  • care/emr/api/viewsets/facility.py (4 hunks)
  • care/emr/api/viewsets/user.py (3 hunks)
  • care/emr/models/base.py (2 hunks)
  • care/emr/models/patient.py (1 hunks)
  • care/emr/models/scheduling/booking.py (1 hunks)
  • care/emr/resources/facility/spec.py (3 hunks)
  • care/emr/resources/user/spec.py (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • care/emr/models/scheduling/booking.py
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (.cursorrules)

**/*.py: Prioritize readability and maintainability; follow Django's coding style guide (PEP 8 compliance).
Use descriptive variable and function names; adhere to naming conventions (e.g., lowercase with underscores for functions and variables).

Files:

  • care/emr/models/patient.py
  • care/emr/api/viewsets/user.py
  • care/emr/models/base.py
  • care/emr/api/viewsets/facility.py
  • care/emr/resources/facility/spec.py
  • care/emr/resources/user/spec.py
**/{models,views,management/commands}/*.py

📄 CodeRabbit inference engine (.cursorrules)

Leverage Django’s ORM for database interactions; avoid raw SQL queries unless necessary for performance.

Files:

  • care/emr/models/patient.py
  • care/emr/models/base.py
🧠 Learnings (9)
📓 Common learnings
Learnt from: aravindm4
Repo: ohcnetwork/care PR: 2585
File: care/facility/api/viewsets/facility_flag.py:14-27
Timestamp: 2024-11-26T05:41:40.226Z
Learning: In the `care/facility/api/viewsets/facility_flag.py` file and similar viewsets in this Django REST Framework project, pagination is added by default. For APIs restricted to superusers using the `IsSuperUser` permission class, additional rate limiting or pagination adjustments are not required.
Learnt from: DraKen0009
Repo: ohcnetwork/care PR: 2620
File: care/facility/models/facility.py:306-311
Timestamp: 2024-11-28T06:16:31.373Z
Learning: In the 'care' project, moving imports of models like `Asset`, `AssetLocation`, `FacilityDefaultAssetLocation`, and `PatientSample` to the module level in `care/facility/models/facility.py` causes circular imports. Therefore, it's acceptable to keep these imports inside the `delete` method of the `Facility` class.
📚 Learning: 2025-07-08T13:27:05.363Z
Learnt from: NikhilA8606
Repo: ohcnetwork/care PR: 3124
File: care/emr/resources/medication/administration/spec.py:163-164
Timestamp: 2025-07-08T13:27:05.363Z
Learning: In the care codebase, all EMR models inherit from EMRBaseModel which inherits from BaseModel (in care/utils/models/base.py). BaseModel provides common fields including created_date and modified_date with auto_now_add=True and auto_now=True respectively. These fields are available on all EMR models through inheritance.

Applied to files:

  • care/emr/models/patient.py
  • care/emr/models/base.py
📚 Learning: 2025-07-15T13:39:29.540Z
Learnt from: NikhilA8606
Repo: ohcnetwork/care PR: 3148
File: care/emr/api/viewsets/consent.py:22-24
Timestamp: 2025-07-15T13:39:29.540Z
Learning: In the care codebase, BaseModel (in care/utils/models/base.py) contains an external_id field which is inherited by all EMR models including Encounter. This allows filtering using field paths like `encounter__external_id` in Django filters.

Applied to files:

  • care/emr/models/patient.py
  • care/emr/models/base.py
  • care/emr/resources/facility/spec.py
📚 Learning: 2024-11-26T05:41:40.226Z
Learnt from: aravindm4
Repo: ohcnetwork/care PR: 2585
File: care/facility/api/viewsets/facility_flag.py:14-27
Timestamp: 2024-11-26T05:41:40.226Z
Learning: In the `care/facility/api/viewsets/facility_flag.py` file and similar viewsets in this Django REST Framework project, pagination is added by default. For APIs restricted to superusers using the `IsSuperUser` permission class, additional rate limiting or pagination adjustments are not required.

Applied to files:

  • care/emr/api/viewsets/user.py
  • care/emr/api/viewsets/facility.py
  • care/emr/resources/facility/spec.py
📚 Learning: 2024-12-02T09:16:55.978Z
Learnt from: DraKen0009
Repo: ohcnetwork/care PR: 2585
File: care/users/api/viewsets/user_flag.py:0-0
Timestamp: 2024-12-02T09:16:55.978Z
Learning: In the `UserFlagViewSet`'s `list_available_flags` method in `care/users/api/viewsets/user_flag.py`, adding extra error handling is not necessary, and returning error messages directly is not preferred.

Applied to files:

  • care/emr/api/viewsets/user.py
  • care/emr/resources/facility/spec.py
  • care/emr/resources/user/spec.py
📚 Learning: 2024-12-02T09:14:53.161Z
Learnt from: DraKen0009
Repo: ohcnetwork/care PR: 2585
File: care/users/api/viewsets/user_flag.py:16-30
Timestamp: 2024-12-02T09:14:53.161Z
Learning: In the `UserFlagViewSet` class in `care/users/api/viewsets/user_flag.py`, we don't need to add configurations for `ordering_fields`, `pagination_class`, or `throttle_classes` because default values are already set globally in our Django REST Framework settings.

Applied to files:

  • care/emr/api/viewsets/user.py
  • care/emr/api/viewsets/facility.py
  • care/emr/resources/user/spec.py
📚 Learning: 2024-11-28T06:13:52.067Z
Learnt from: DraKen0009
Repo: ohcnetwork/care PR: 2620
File: care/facility/models/bed.py:62-65
Timestamp: 2024-11-28T06:13:52.067Z
Learning: In `care/facility/models/bed.py`, when deleting a `Bed` instance in the `delete` method, if we're only adding validation and performing a single update, adding an atomic transaction is not necessary.

Applied to files:

  • care/emr/api/viewsets/user.py
📚 Learning: 2025-07-30T12:06:18.804Z
Learnt from: rithviknishad
Repo: ohcnetwork/care PR: 3176
File: care/emr/api/viewsets/inventory/product_knowledge.py:33-40
Timestamp: 2025-07-30T12:06:18.804Z
Learning: In EMR viewsets using EMRUpsertMixin, the upsert method properly handles authorization by delegating to handle_update() and handle_create(), which in turn call authorize_update() and authorize_create() respectively. No separate authorize_upsert() method is needed.

Applied to files:

  • care/emr/api/viewsets/user.py
📚 Learning: 2024-11-28T06:16:31.373Z
Learnt from: DraKen0009
Repo: ohcnetwork/care PR: 2620
File: care/facility/models/facility.py:306-311
Timestamp: 2024-11-28T06:16:31.373Z
Learning: In the 'care' project, moving imports of models like `Asset`, `AssetLocation`, `FacilityDefaultAssetLocation`, and `PatientSample` to the module level in `care/facility/models/facility.py` causes circular imports. Therefore, it's acceptable to keep these imports inside the `delete` method of the `Facility` class.

Applied to files:

  • care/emr/api/viewsets/facility.py
  • care/emr/resources/facility/spec.py
🧬 Code graph analysis (3)
care/emr/api/viewsets/user.py (3)
care/emr/models/user.py (2)
  • UserFlag (7-36)
  • get_all_flags (35-36)
care/utils/registries/feature_flag.py (6)
  • FlagNotFoundError (9-10)
  • FlagRegistry (22-68)
  • FlagType (13-15)
  • register (26-29)
  • unregister (32-36)
  • get_all_flags (60-62)
care/users/models.py (1)
  • get_all_flags (424-425)
care/emr/api/viewsets/facility.py (4)
care/emr/models/facility.py (2)
  • FacilityFlag (7-36)
  • get_all_flags (35-36)
care/emr/resources/facility/spec.py (3)
  • FacilityCreateSpec (68-100)
  • FacilityFlagCreateSpec (165-182)
  • FacilityFlagReadSpec (185-193)
care/utils/registries/feature_flag.py (6)
  • FlagNotFoundError (9-10)
  • FlagRegistry (22-68)
  • FlagType (13-15)
  • register (26-29)
  • unregister (32-36)
  • get_all_flags (60-62)
care/emr/models/base.py (2)
  • invalidate_cache (94-98)
  • get_all_flags (82-91)
care/emr/resources/facility/spec.py (3)
care/utils/shortcuts.py (1)
  • get_object_or_404 (6-15)
care/emr/models/facility.py (2)
  • FacilityFlag (7-36)
  • check_facility_has_flag (31-32)
care/utils/registries/feature_flag.py (1)
  • FlagNotFoundError (9-10)
🪛 Ruff (0.14.7)
care/emr/api/viewsets/user.py

219-219: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


249-249: Unused method argument: request

(ARG002)

care/emr/api/viewsets/facility.py

247-247: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


277-277: Unused method argument: request

(ARG002)

care/emr/resources/facility/spec.py

160-160: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


175-175: Avoid specifying long messages outside the exception class

(TRY003)


186-186: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

care/emr/resources/user/spec.py

232-232: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


247-247: Avoid specifying long messages outside the exception class

(TRY003)


258-258: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

⏰ 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). (1)
  • GitHub Check: Test / Test
🔇 Additional comments (17)
care/emr/models/base.py (3)

3-3: LGTM!

The new imports are appropriate for the flag caching infrastructure.

Also applies to: 7-7


34-34: Cache TTL is reasonable.

One day provides a good balance between cache effectiveness and flag freshness, though you could consider increasing this since feature flags typically don't change frequently.


40-43: Class attribute pattern is correct; ignore static analysis hint.

These attributes are intentionally mutable template values meant to be overridden by concrete subclasses (FacilityFlag, UserFlag). Annotating them with ClassVar would be incorrect since they're not truly class-level constants.

care/emr/resources/facility/spec.py (3)

158-163: Spec configuration looks good.

The base spec properly excludes the facility field for controlled serialization.


169-178: Duplicate flag validation is now correct.

The validation logic properly prevents duplicate flags by raising an error when the flag already exists. This addresses the previously reported issue where the logic was inverted.


185-194: Read spec serialization is properly structured.

The nested facility serialization via FacilityReadSpec and flag exposure are correctly implemented.

care/emr/resources/user/spec.py (3)

230-235: Base spec follows standard pattern.

The spec properly excludes the user field for controlled serialization.


241-250: Duplicate flag prevention is correctly implemented.

The validation properly raises an error when a user already has the flag, preventing duplicates as intended.


257-266: Serialization logic is sound.

Properly nests the user data and exposes the flag information.

care/emr/api/viewsets/facility.py (5)

1-1: LGTM!

All new imports are properly utilized in the flag management implementation.

Also applies to: 15-23, 25-25, 29-30, 46-46


236-239: Filter configuration is appropriate.

Enables filtering by exact flag name and facility external_id.


241-252: Viewset configuration follows established patterns.

Superuser-only access is properly enforced, and pagination/throttling defaults apply as expected.

Based on learnings, superuser-restricted endpoints don't require explicit pagination adjustments.


266-274: Safe unregister logic correctly prevents premature flag removal.

The implementation properly checks for remaining non-deleted flags before unregistering, addressing the previously reported concern about flags being removed while still in use.


276-284: Available flags endpoint follows expected pattern.

Returns registered flags or an appropriate error response. The unused request parameter is required by the DRF action decorator signature.

care/emr/api/viewsets/user.py (3)

13-20: Imports are properly structured.

All new imports support the user flag management functionality.

Also applies to: 23-23, 28-29, 44-44


208-224: User flag viewset mirrors facility flag patterns.

Configuration is consistent with the facility flag implementation and follows established conventions.


248-256: Available flags endpoint is consistent.

Follows the same pattern as the facility flags endpoint with appropriate error handling.

Comment on lines +258 to +264
def perform_destroy(self, instance):
with transaction.atomic():
super().perform_destroy(instance)
FacilityFlag.invalidate_cache(instance.facility, instance.flag)
transaction.on_commit(
lambda: self._safe_unregister_flag_if_unused(instance.flag, instance.id)
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Wrong argument type passed to invalidate_cache.

Line 261 passes instance.facility (a Facility object) to invalidate_cache, which expects entity_id: int. This will cause a type error or incorrect cache key formatting.

         with transaction.atomic():
             super().perform_destroy(instance)
-            FacilityFlag.invalidate_cache(instance.facility, instance.flag)
+            FacilityFlag.invalidate_cache(instance.facility_id, instance.flag)
             transaction.on_commit(
                 lambda: self._safe_unregister_flag_if_unused(instance.flag, instance.id)
             )
🤖 Prompt for AI Agents
In care/emr/api/viewsets/facility.py around lines 258 to 264, perform_destroy
calls FacilityFlag.invalidate_cache with instance.facility (a Facility object)
but the function expects entity_id: int; change the call to pass the facility's
integer id (use instance.facility_id or instance.facility.id) so the correct
type/format is provided, keeping the transaction.atomic and on_commit logic
unchanged.

Comment on lines +230 to +236
def perform_destroy(self, instance):
with transaction.atomic():
super().perform_destroy(instance)
UserFlag.invalidate_cache(instance.user, instance.flag)
transaction.on_commit(
lambda: self._safe_unregister_flag_if_unused(instance.flag, instance.id)
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Wrong argument type passed to invalidate_cache.

Line 233 passes instance.user (a User object) instead of instance.user_id (int) to invalidate_cache. This is the same issue present in the facility viewset.

         with transaction.atomic():
             super().perform_destroy(instance)
-            UserFlag.invalidate_cache(instance.user, instance.flag)
+            UserFlag.invalidate_cache(instance.user_id, instance.flag)
             transaction.on_commit(
                 lambda: self._safe_unregister_flag_if_unused(instance.flag, instance.id)
             )
🤖 Prompt for AI Agents
In care/emr/api/viewsets/user.py around lines 230 to 236, the call to
UserFlag.invalidate_cache is passing instance.user (a User object) instead of
the user id; change the call to pass instance.user_id (int). Update the same
pattern where found (e.g., the facility viewset) to use instance.user_id, and
keep the surrounding transaction.atomic and transaction.on_commit logic
unchanged.

Comment on lines +48 to +56
def save(self, *args, **kwargs):
self.validate_flag(self.flag)
cache.delete(
self.cache_key_template.format(
entity_id=self.entity_id, flag_name=self.flag
)
)
cache.delete(self.all_flags_cache_key_template.format(entity_id=self.entity_id))
return super().save(*args, **kwargs)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Cache invalidation happens before persistence completes.

Currently, you delete cache entries before super().save(), which means if the save fails (e.g., validation error, DB constraint), the cache is already invalidated. Consider moving invalidation after the save or using a post-save signal.

Apply this approach:

 def save(self, *args, **kwargs):
     self.validate_flag(self.flag)
+    result = super().save(*args, **kwargs)
     cache.delete(
         self.cache_key_template.format(
             entity_id=self.entity_id, flag_name=self.flag
         )
     )
     cache.delete(self.all_flags_cache_key_template.format(entity_id=self.entity_id))
-    return super().save(*args, **kwargs)
+    return result
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def save(self, *args, **kwargs):
self.validate_flag(self.flag)
cache.delete(
self.cache_key_template.format(
entity_id=self.entity_id, flag_name=self.flag
)
)
cache.delete(self.all_flags_cache_key_template.format(entity_id=self.entity_id))
return super().save(*args, **kwargs)
def save(self, *args, **kwargs):
self.validate_flag(self.flag)
result = super().save(*args, **kwargs)
cache.delete(
self.cache_key_template.format(
entity_id=self.entity_id, flag_name=self.flag
)
)
cache.delete(self.all_flags_cache_key_template.format(entity_id=self.entity_id))
return result
🤖 Prompt for AI Agents
In care/emr/models/base.py around lines 48 to 56, cache entries are being
deleted before the model is persisted which can leave cache stale if save()
fails; move the cache invalidation to occur after calling super().save(*args,
**kwargs) (or register a post-save signal handler) so deletion only happens on
successful persistence, ensuring you still call validate_flag(self.flag) prior
to saving and delete both the per-entity flag key and the all-flags key after
the save completes.


from django.conf import settings
from django.db.models.functions import Lower, Trim
from django.shortcuts import get_object_or_404
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use the project's custom get_object_or_404 wrapper.

The codebase provides a custom get_object_or_404 in care.utils.shortcuts that properly handles validation errors by raising 404. You're importing from django.shortcuts instead, which is already imported on line 47 from the correct location for use elsewhere in this file.

-from django.shortcuts import get_object_or_404
+from care.utils.shortcuts import get_object_or_404
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from django.shortcuts import get_object_or_404
from care.utils.shortcuts import get_object_or_404
🤖 Prompt for AI Agents
In care/emr/resources/facility/spec.py around line 5, the import is using
django.shortcuts.get_object_or_404 instead of the project's custom wrapper;
replace the django import with the project's wrapper by importing
get_object_or_404 from care.utils.shortcuts (or simply remove this duplicate
django import and rely on the existing care.utils.shortcuts import already
present at line 47) so validation errors are handled correctly.

Comment on lines +105 to 106
except ValueError as e:
raise ValueError("Invalid Email") from e
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, check the actual file to see the context around lines 105-106
echo "=== File context around lines 105-106 ==="
sed -n '95,115p' care/emr/resources/user/spec.py

echo -e "\n=== Check imports at top of file ==="
head -30 care/emr/resources/user/spec.py

Repository: ohcnetwork/care

Length of output: 1820


🏁 Script executed:

#!/bin/bash
# Search for validate_email usage patterns in codebase
echo "=== validate_email usage patterns ==="
rg "validate_email" -A 5 -B 2 --type py

Repository: ohcnetwork/care

Length of output: 1219


🏁 Script executed:

#!/bin/bash
# Check what exception validate_email raises by looking at Django docs and actual behavior
python3 << 'EOF'
try:
    from django.core.validators import validate_email
    from django.core.exceptions import ValidationError
    
    # Test with invalid email
    try:
        validate_email("invalid")
    except ValidationError as e:
        print("validate_email raises: ValidationError")
        print(f"Exception details: {e}")
    except ValueError as e:
        print("validate_email raises: ValueError")
        print(f"Exception details: {e}")
except ImportError:
    print("Django not available in this environment")
EOF

Repository: ohcnetwork/care

Length of output: 145


🏁 Script executed:

#!/bin/bash
# Check git history to confirm this was changed from ValidationError to ValueError
echo "=== Git log for the file ==="
git log --oneline -20 care/emr/resources/user/spec.py

echo -e "\n=== Git diff to see recent changes ==="
git diff HEAD~5 care/emr/resources/user/spec.py | head -100

Repository: ohcnetwork/care

Length of output: 455


🏁 Script executed:

#!/bin/bash
# Verify Django's validate_email exception type from documentation
# Since validate_email is from django.core.validators, it should raise ValidationError
rg "from django.core.exceptions import" --type py | head -5
rg "ValidationError" care/emr/resources/user/spec.py

Repository: ohcnetwork/care

Length of output: 567


Incorrect exception handler: validate_email raises ValidationError, not ValueError.

Django's validate_email raises ValidationError from django.core.exceptions, not ValueError. The current except clause will not catch the validation error, allowing it to propagate uncaught. Change line 105 to except ValidationError as e: and add the necessary import: from django.core.exceptions import ValidationError.

🧰 Tools
🪛 Ruff (0.14.7)

106-106: Avoid specifying long messages outside the exception class

(TRY003)

🤖 Prompt for AI Agents
In care/emr/resources/user/spec.py around lines 105 to 106, the except clause
incorrectly catches ValueError but django.core.validators.validate_email raises
django.core.exceptions.ValidationError; change the handler to except
ValidationError as e and add the missing import from django.core.exceptions
import ValidationError at the top of the file so the ValidationError type is
available for the except clause.

@praffq
Copy link
Contributor Author

praffq commented Jan 14, 2026

completed in - #3460

@praffq praffq closed this Jan 14, 2026
@praffq praffq deleted the prafful/feat/user-and-facility-flag branch January 14, 2026 08:09
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.

3 participants