Typorithm · medium

Partial input and read-only fields have separate meanings

pythondjangodrfindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

Partial input and read-only fields have separate meanings

0%

Concept and contract

Validate only submitted patch fields while preserving server-controlled state.

A creation request often requires fields that a partial update intentionally omits. partial=True changes missing-field handling so a patch can submit only its intended changes. It does not grant permission to change fields the server owns.

The fixture uses a serializer with a required title, positive slots and a read-only owner. It validates a patch, merges accepted fields into a separate owned mapping and returns the unchanged original for comparison. No database or live user record is modified.

Mechanism and worked example

A submitted title is trimmed and validated, while an omitted slots value is not invented as a new update.

A submitted title is trimmed and validated, while an omitted slots value is not invented as a new update. The read-only owner input is ignored under this DRF policy and never enters validated_data. The effective owner therefore remains the one already established by the server fixture.

The helper copies the original mapping before applying validated changes. Tests check both the next value and the original. This distinguishes accepting a valid patch from accidentally mutating caller data or overwriting omitted fields with defaults.

Complete worked example

python

from drf_lab import setup
setup()

from rest_framework import serializers

class Patch(serializers.Serializer):
    title = serializers.CharField()
    slots = serializers.IntegerField(min_value=1)
    owner = serializers.CharField(read_only=True)

def inspect_example(payload):
    original = {"title": "QA", "slots": 1, "owner": "alice"}
    serializer = Patch(data=payload, partial=True)
    if not serializer.is_valid():
        return {"errors": {key: [error.code for error in values]
                           for key, values in serializer.errors.items()}}
    updated = dict(original)
    updated.update(serializer.validated_data)
    return {"value": updated, "original": original}
Reasoning
A submitted title is trimmed and validated, while an omitted slots value is not invented as a new update. The read-only owner input is ignored under this DRF policy and never enters validated_data. The effective owner therefore remains the one already established by the server fixture. The helper copies the original mapping before applying validated changes. Tests check both the next value and the original. This distinguishes accepting a valid patch from accidentally mutating caller data or overwriting omitted fields with defaults.

Focused decision: context is in the complete example

python

serializer = Patch(data=payload, partial=True)
serializer.is_valid(raise_exception=True)
updated = dict(original)
updated.update(serializer.validated_data)
Focused decision
Reconstruct this focused decision after the warm-up. The complete imports, model or application, and test client remain in the worked example. Typing records practice; only the downloadable computer lab executes Python.

Counterexamples and common mistakes

The counterexample makes owner writable.

The counterexample makes owner writable. A client-supplied owner can then enter the accepted patch and change the selected ownership value. Even a string that parses correctly is not an authorized identity decision.

Read-only is a serializer input policy, not an object-level permission check. A caller might still be unauthorized to patch the selected record at all. Likewise, partial validation of a patch does not automatically recheck every invariant that depends on both submitted and existing state. Merge and validate such relationships deliberately.

Controlled mistake: observe the actual wrong behavior

python

from drf_lab import setup
setup()

from rest_framework import serializers

class Patch(serializers.Serializer):
    title = serializers.CharField()
    slots = serializers.IntegerField(min_value=1)
    owner = serializers.CharField(required=False)

def inspect_example(payload):
    original = {"title": "QA", "slots": 1, "owner": "alice"}
    serializer = Patch(data=payload, partial=True)
    if not serializer.is_valid():
        return {"errors": {key: [error.code for error in values]
                           for key, values in serializer.errors.items()}}
    updated = dict(original)
    updated.update(serializer.validated_data)
    return {"value": updated, "original": original}
Reasoning
The counterexample makes owner writable. A client-supplied owner can then enter the accepted patch and change the selected ownership value. Even a string that parses correctly is not an authorized identity decision. Read-only is a serializer input policy, not an object-level permission check. A caller might still be unauthorized to patch the selected record at all. Likewise, partial validation of a patch does not automatically recheck every invariant that depends on both submitted and existing state. Merge and validate such relationships deliberately.

Independent practice

Limit submitted titles to eight characters while keeping partial updates and read-only ownership.

Limit submitted titles to eight characters while keeping partial updates and read-only ownership. A missing title should remain acceptable, but an explicitly overlong title must fail. Do not make the title required again merely to apply its length bound.

Test an eight-character title, a nine-character title and a patch containing only slots plus a forged owner. Preserve the existing owner and unchanged original in every accepted case. This is a bounded field-policy task rather than a generic record merge utility.

Step 1: Bound supplied titles to eight characters without requiring omitted values or accepting a forged owner. Step 2: Retain the working imports and application structure. Change the stated policy, run the supplied practice cases, then explain the newly accepted or rejected boundary.

Practice starter: solve before reading the solution

python

from drf_lab import setup
setup()

from rest_framework import serializers

class Patch(serializers.Serializer):
    title = serializers.CharField()
    slots = serializers.IntegerField(min_value=1)
    owner = serializers.CharField(read_only=True)

def inspect_example(payload):
    original = {"title": "QA", "slots": 1, "owner": "alice"}
    serializer = Patch(data=payload, partial=True)
    if not serializer.is_valid():
        return {"errors": {key: [error.code for error in values]
                           for key, values in serializer.errors.items()}}
    updated = dict(original)
    updated.update(serializer.validated_data)
    return {"value": updated, "original": original}

# Independent task: Bound supplied titles to eight characters without requiring omitted values or accepting a forged owner.
Reasoning
Bound supplied titles to eight characters without requiring omitted values or accepting a forged owner.
  • Hint 1: Find the one field or branch that owns this policy.
  • Hint 2: Compare the first changed acceptance case with the retained baseline.

Solution, tests and trade-offs

The solution adds max_length=8 to the title field.

The solution adds max_length=8 to the title field. The serializer still runs with partial=True, so omitted values remain omitted from the accepted patch. Server-owned data does not become writable as a side effect of the new constraint.

In production, apply the accepted change through a storage operation that handles concurrent updates and ownership. Consider a dedicated writable serializer when creation and update policies differ significantly. A shallow mapping copy does not establish deep immutability or transactional consistency.

Practice solution and acceptance checks

python

from drf_lab import setup
setup()

from rest_framework import serializers

class Patch(serializers.Serializer):
    title = serializers.CharField(max_length=8)
    slots = serializers.IntegerField(min_value=1)
    owner = serializers.CharField(read_only=True)

def inspect_example(payload):
    original = {"title": "QA", "slots": 1, "owner": "alice"}
    serializer = Patch(data=payload, partial=True)
    if not serializer.is_valid():
        return {"errors": {key: [error.code for error in values]
                           for key, values in serializer.errors.items()}}
    updated = dict(original)
    updated.update(serializer.validated_data)
    return {"value": updated, "original": original}
Reasoning
The solution adds max_length=8 to the title field. The serializer still runs with partial=True, so omitted values remain omitted from the accepted patch. Server-owned data does not become writable as a side effect of the new constraint. In production, apply the accepted change through a storage operation that handles concurrent updates and ownership. Consider a dedicated writable serializer when creation and update policies differ significantly. A shallow mapping copy does not establish deep immutability or transactional consistency.

Interview reasoning and next step

Explain why PATCH omission differs from explicit null or an empty string.

Explain why PATCH omission differs from explicit null or an empty string. Then identify the test that catches a writable owner even when the endpoint still validates ordinary field types. Separate patch shape, field authority and object access in the explanation.

The next lesson uses ModelSerializer and real owned database rows. Automatic field generation can reduce repetition, but the public field set and server-supplied ownership must remain deliberate.

Optional stretch — after the core task works: Add one independently reasoned boundary case and explain its expected value before running it.

Computer practice: https://applydjinn.com/typorithm/drf-lab-20261105.zip. Install pinned dependencies in an isolated Python environment. Run verify.py, then change the working exercise baseline and run --practice drf-updates. The real DRF clients, serializers, schemas and SQLite transactions use owned fixtures only. No production database, feed, account or paid service is used. This lab executes Python; the PC typing trainer and native reading progress do not execute it.

Validate only submitted patch fields while preserving server-controlled state.