In Django REST Framework, how does a field's source= argument differ from SerializerMethodField for exposing values the model does not store in a column?
answer
- attribute path versus method call
- dotted paths and callables
- always read-only
- get_<field_name> and self.context
basics
~20 ssource= points a normal field at an attribute path, property, zero-argument method or the whole object, and can be writable. SerializerMethodField is always read-only and calls get_<field_name>(obj) on the serializer, where self.context, such as the request, is available.
solid answer
~40 s`source` tells a regular field where its value lives: `CharField(source="category.name", read_only=True)` follows the relation, a property or zero-argument method on the model is called automatically, and `source="*"` hands the whole object to the field. Because it is a real field it can also accept input, though a writable dotted source is rejected by `ModelSerializer`'s default `create()` and `update()` unless you write them yourself. `SerializerMethodField` is always read-only: DRF calls `get_<field_name>(self, obj)`, or the method named by `method_name`, and puts whatever it returns in the output. I use `source` for plain attribute access and `SerializerMethodField` when the value needs logic or `self.context`, such as whether the requesting user has a product on a wishlist. Both can trigger a query per row, which matters on list endpoints.
code
python · 19 linesfrom rest_framework import serializers
from catalog.models import Product
class ProductListSerializer(serializers.ModelSerializer):
category_name = serializers.CharField(source="category.name", read_only=True, allow_null=True)
display_price = serializers.CharField(read_only=True) # a model property, same name
on_wishlist = serializers.SerializerMethodField()
class Meta:
model = Product
fields = ["id", "sku", "name", "category_name", "display_price", "on_wishlist"]
def get_on_wishlist(self, obj):
request = self.context.get("request")
if request is None or not request.user.is_authenticated:
return False
return obj.wishlisted_by.filter(pk=request.user.pk).exists()go deeper
Know that source= renames or points a field at another attribute, and that SerializerMethodField calls a get_ method to compute a read-only value.
Explain dotted paths, callables and source='*', why SerializerMethodField is always read-only, and how self.context carries the request.
Anticipate null relations dropping keys, writable dotted sources breaking default create(), and per-row queries on list endpoints.
Set conventions for derived fields so the API contract stays declared and documented while expensive computed values are kept off hot list endpoints.
## Two ways to reach data outside the column list A catalog API often returns values that are not columns on the `Product` table: the category's name, a `display_price` property, whether the current user has the product on a wishlist. Django REST Framework offers two mechanisms, and they differ in direction, flexibility and visibility. ## What source= accepts `source` is an argument every serializer field accepts. It defaults to the field name, and DRF asserts that you do not set it to the same value, so `name = CharField(source="name")` fails with an "It is redundant to specify `source=...`" error. | Form | Example | What DRF does on output | |---|---|---| | attribute | `source="title"` | reads `obj.title` | | dotted path | `source="category.name"` | reads `obj.category.name`, one step at a time | | property or method | `source="display_price"` | reads it and, if it is a callable needing no arguments, calls it | | dict key | `source="meta.colour"` on dict input | looks up keys when the object is a mapping | | whole object | `source="*"` | passes the entire object to the field | On input, the validated value is written back at the same path, so a dotted source produces a nested dict in `validated_data`. `ModelSerializer`'s default `create()` and `update()` refuse such **writable dotted-source fields** with an error telling you to write the method yourself or set `read_only=True`. ## SerializerMethodField - It is **always read-only**; DRF forces `read_only=True` and `source="*"` in its constructor. - The method defaults to **`get_<field_name>`** and receives the object being serialized; `method_name=` picks another method. - It runs on the serializer instance, so it can use **`self.context`**. Generic views put `request`, `format` and `view` in the context, which is what makes per-user values possible. - It is a declared field, so it appears in `repr()`, in `Meta.fields` and in schema generation, unlike a key added by overriding `to_representation()`. ## source='*' for grouping flat columns `source="*"` is also useful for writes. A custom field declared as `dimensions = DimensionsField(source="*")` receives the whole product on output and can return `{"width": ..., "height": ..., "depth": ...}` built from three model columns. On input, its `to_internal_value()` returns a dict such as `{"width_mm": 120, "height_mm": 40, "depth_mm": 15}`, and because the source is `*`, DRF **merges that dict into the top level** of `validated_data`. The default `create()` and `update()` then see three ordinary model fields, so flat columns can be presented as one object without writing either method. ## Choosing between them | Need | Better fit | |---|---| | a related object's attribute, read-only | `source="category.name"` with `read_only=True` | | a model property or zero-argument method | `source="display_price"` | | a value accepted on input under a different name | `source` on a writable field | | logic that combines several attributes | `SerializerMethodField` | | a value that depends on the request or user | `SerializerMethodField` using `self.context` | ## Traps worth knowing - **A `None` in the middle of a dotted path.** If `category` is null, reading `category.name` raises `AttributeError` inside DRF. For a read-only field, which is not required, DRF then **skips the key entirely**, so the output silently lacks `category_name`. Add `allow_null=True` (the key appears with `None`) or a `default`. - **A missing related object.** When a lookup along the path raises `ObjectDoesNotExist`, as a missing reverse one-to-one does, DRF returns `None` for the value. - **Errors inside a callable.** An `AttributeError` or `KeyError` raised by a method named in `source` is re-raised as a `ValueError`, so a bug is not mistaken for a missing attribute. - **Missing context.** A serializer instantiated by hand, outside a generic view, has no `request` in `self.context` unless you pass `context={"request": request}`; a method field that reads it then fails. - **Query cost.** Both a dotted `source` across a relation and a method field that queries run once per object; on a list endpoint that multiplies. How to load related data up front is covered with nested payload cost.
- Why does a DRF read-only field with source='category.name' sometimes vanish from the output?When `category` is null, reading `name` on `None` raises `AttributeError`. DRF then falls back: a `default` is used if set, `None` if `allow_null=True`, and otherwise, because a read-only field is not required, the field is skipped and the key is omitted. Set `allow_null=True` to keep a stable output shape.
- Can a DRF SerializerMethodField accept input on create or update?No. Its constructor forces `read_only=True`, so it is ignored during validation and never reaches `validated_data`. To accept a value, add a writable field, optionally with a different `source`, and handle it in validation or in `create()`/`update()`.
source= is a street address: DRF walks to that spot and reads what is there, and a parcel can be delivered back to the same address. SerializerMethodField is a phone call to an assistant: you get whatever answer they work out, possibly after checking who is asking, but you cannot send anything back through that call.
saying these in an interview costs you the question
- SerializerMethodField can be made writable by passing read_only=False.
- source='name' on a field called name is harmless and just explicit.
- A dotted source like category.name always outputs null when category is missing.
- source can only point at model fields, not properties or methods.
- SerializerMethodField runs without cost because DRF caches its result per request.