-
Notifications
You must be signed in to change notification settings - Fork 544
User and facility flag viewset #3044
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
Conversation
📝 WalkthroughWalkthroughMoved 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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
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.
Actionable comments posted: 5
🧹 Nitpick comments (9)
care/users/migrations/0024_delete_userflag.py (1)
1-16: Migration looks good but could use quotes consistencyThe 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 viewsetsThe 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 onlycare/emr/models/user.py (3)
23-28: Consider adding a more descriptive name for the UniqueConstraintWhile 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 methodThe 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 methodSimilar 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_flagwill cache negative look-ups foreverWith
cache.get_or_setthe lambda is evaluated once and its result (True/False) is stored for a whole day.
If a flag is subsequently added, consumers callingcheck_entity_has_flagwill still receive the oldFalsevalue until the TTL expires.Consider:
- Bumping the TTL down, or
- Explicitly invalidating this key in the concrete model’s
save/deletemethods (you already clear it there – good), and- 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, yetFacilityFlagReadSpecmanually re-injects a nestedfacilitydict. 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 onflagfor faster look-ups.Both
FacilityFlagandUserFlagare queried byflag(see the unique constraint and the new viewsets’ filters).
Addingdb_index=Trueonflagwill 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:idshould be serialised as a string for consistency.Elsewhere (
UserRetrieveSpec, etc.)external_idis stringified; returning a rawUUIDhere 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
📒 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
UserFlagfromcare.emr.models.useras 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
BaseFlaghas 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
FacilityFlagnow points to the model's new location incare.emr.models.facilityrather 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
FlagTypeinherit fromstrin addition toenum.Enumsimplifies 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 refactoringThis 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 importsThe 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 importThe import of FacilityFlagViewSet is properly placed within the facility viewsets group.
74-74: Updated UserViewSet import to include UserFlagViewSetThe 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 modelThis 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 appThe 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 relocationThe
get_all_flagsmethod correctly uses the importedUserFlagclass, 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 modelThis change properly updates the
booked_byfield 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 implementationThe 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_controllerAPI contract is unclear
permissions_controllercurrently returns a bare boolean.
Double-check thatEMRBaseViewSetinterprets this correctly; many DRF permission hooks expect either a list/tuple of permission classes or an object with ahas_permissionmethod. A silentFalsehere could inadvertently expose or block the endpoint.care/emr/api/viewsets/facility.py (1)
214-221:⚠️ Potential issueWrong flag type passed to registry – returns user flags instead of facility flags.
list_available_flagslives inFacilityFlagViewSet, yet asks forFlagType.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 ReportAttention: Patch coverage is
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. 🚀 New features to boost your workflow:
|
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.
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_CASEfor 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_setfor 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
📒 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_createorbulk_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_createorbulk_updatecalls on any models inheriting fromcare/emr/models/base.py, so the overriddensaveis 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.
…lag' into prafful/feat/user-and-facility-flag
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.
Actionable comments posted: 5
🧹 Nitpick comments (4)
care/emr/models/patient.py (1)
145-153: Usesettings.AUTH_USER_MODELfor 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 usesettings.AUTH_USER_MODEL, while these four model FK fields (inpatient.py,valueset.py, andorganization.py) hard-code the string. For consistency and future-proofing, consider aligningPatientUser.userto usesettings.AUTH_USER_MODELinstead.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 bydeleted=Falseto 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_flagsdoesn't explicitly filter bydeleted=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=Falsewould 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
📒 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.pycare/emr/api/viewsets/user.pycare/emr/models/base.pycare/emr/api/viewsets/facility.pycare/emr/resources/facility/spec.pycare/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.pycare/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.pycare/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.pycare/emr/models/base.pycare/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.pycare/emr/api/viewsets/facility.pycare/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.pycare/emr/resources/facility/spec.pycare/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.pycare/emr/api/viewsets/facility.pycare/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.pycare/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
ClassVarwould 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
requestparameter 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.
| 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) | ||
| ) |
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.
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.
| 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) | ||
| ) |
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.
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.
| 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) |
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.
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.
| 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 |
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.
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.
| 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.
| except ValueError as e: | ||
| raise ValueError("Invalid Email") from e |
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.
🧩 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.pyRepository: 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 pyRepository: 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")
EOFRepository: 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 -100Repository: 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.pyRepository: 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.
|
completed in - #3460 |
Proposed Changes
Associated Issue
Architecture changes
Merge Checklist
/docsOnly 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
Bug Fixes
Chores
✏️ Tip: You can customize this high-level summary in your review settings.