Skip to content

Fix: Facility PATCH endpoint behaving like PUT#3524

Open
pratx08 wants to merge 5 commits intoohcnetwork:developfrom
pratx08:develop
Open

Fix: Facility PATCH endpoint behaving like PUT#3524
pratx08 wants to merge 5 commits intoohcnetwork:developfrom
pratx08:develop

Conversation

@pratx08
Copy link

@pratx08 pratx08 commented Feb 10, 2026

Proposed Changes

  • Fixed PATCH endpoint update flow in EMRUpdateMixin to correctly propagate partial update intent (partial=True) instead of defaulting to full update behavior. BCP Guidelines.
  • Added FacilityUpdateSpec with optional fields for update operations.
  • Updated FacilityViewSet to use pydantic_update_model = FacilityUpdateSpec.
  • Added regression test care/emr/tests/test_facility_api.py to ensure PATCH /api/v1/facility/{id}/ works with partial payload.

Root Cause

  • partial_update() was not passing partial semantics through shared update handling.
  • Facility update path was using create and required field validation, causing PATCH to act like PUT.

Validation

  • Manual API validation in Postman:

Before Fix
image

After Fix
image

Associated Issue

  • Reproduced bug: PATCH /api/v1/facility/{id}/ with payload { "name": "..." } returned 400 with missing required fields.

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

Release Notes

  • New Features

    • Facility records can now be updated using partial updates (PATCH requests), allowing users to modify specific fields without affecting others.
    • Added validation to ensure facility names remain unique during updates.
  • Tests

    • Added test coverage for facility partial update operations.

@pratx08 pratx08 requested a review from a team as a code owner February 10, 2026 16:35
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 10, 2026

📝 Walkthrough

Walkthrough

This change implements partial update support for facility resources by refactoring the base mixin to explicitly pass the partial flag through the update flow, introducing a new FacilityUpdateSpec with optional fields and validation logic, and wiring it into the facility viewset. Test coverage confirms the partial update behavior.

Changes

Cohort / File(s) Summary
Base Update Mixin Refactoring
care/emr/api/viewsets/base.py
Modified EMRUpdateMixin.handle_update to accept an explicit partial parameter. The update() method now extracts partial from kwargs and passes it forward, while partial_update() calls update() with partial=True. Removed reliance on extracting partial from serializer state. Updated deserialization logic to use the explicit partial parameter.
Facility Spec & Viewset Integration
care/emr/resources/facility/spec.py, care/emr/api/viewsets/facility.py
Introduced FacilityUpdateSpec extending FacilityCreateSpec with all fields optional. Added field-level validator for name uniqueness with update context awareness (excludes current object). Implemented perform_extra_deserialization() to map geo_organization from external ID and translate facility_type. Wired spec into FacilityViewSet via pydantic_update_model attribute.
Partial Update Test Coverage
care/emr/tests/test_facility_api.py
Added TestFacilityPartialUpdate test class to verify PATCH requests update only provided fields while preserving unchanged ones. Test creates a facility, sends partial payload with only name, and asserts other fields remain intact with HTTP 200 response.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the main issue being fixed: the PATCH endpoint incorrectly behaving like PUT instead of implementing proper partial updates.
Description check ✅ Passed The description follows the template structure with Proposed Changes, Associated Issue, and a completed Merge Checklist. All critical information about the fix, root cause, and validation is included with relevant links and screenshots.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
care/emr/resources/facility/spec.py (2)

222-233: Missing blank line before the @field_validator decorator (line 234).

A blank line between the field declarations and the first method/decorator improves readability — just a small formatting thing that surely won't bother anyone either way.

🔧 Add blank line
     features: list[int] | None = None
+
     `@field_validator`("name")

234-255: Remove the duplicate validatorFacilityCreateSpec already defines validate_name_uniqueness, which is inherited by the subclass.

In Pydantic v2, when a subclass redefines a validator method with the same name, it overrides the parent's method entirely (standard Python inheritance). Since both implementations are identical, the child's version is redundant. Removing it would eliminate the code duplication and let the parent's inherited validator handle the validation, keeping the logic in a single place.

♻️ Remove the duplicate validator
     features: list[int] | None = None
-    `@field_validator`("name")
-    `@classmethod`
-    def validate_name_uniqueness(cls, v, info: ValidationInfo):
-        if not v:
-            return v
-
-        normalized_name = v.strip().lower()
-        context = info.context or {}
-        is_update = context.get("is_update", False)
-        obj = context.get("object")
-
-        qs = Facility.objects.annotate(normalized_name=Lower(Trim("name"))).filter(
-            normalized_name=normalized_name
-        )
-
-        if is_update and obj:
-            qs = qs.exclude(id=obj.id)
-
-        if qs.exists():
-            raise ValueError("A facility with this name already exists")
-
-        return v
care/emr/tests/test_facility_api.py (1)

7-44: Solid regression test — covers the core PATCH-with-partial-payload scenario.

A couple of additional cases that might be nice to have (whenever you get around to it, no rush of course):

  • A test verifying that PATCH with a duplicate name returns a validation error.
  • A test verifying that a full PUT still requires all mandatory fields (to confirm PUT wasn't accidentally broken).
  • A test for updating a field other than name (e.g., description alone) to broaden coverage.

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant