Propagate model field defaults to ModelSerializer fields#9997
Conversation
Shrikantgiri25
left a comment
There was a problem hiding this comment.
Great work! Quick suggestion: Add tests for:
- Callable defaults (
uuid.uuid4) - ensure they execute correctly - Choice fields with default - validates the original #7469 fix end-to-end
- OpenAPI schema (optional) - verify defaults appear in docs
These cover the exact scenarios from the linked issues.
There was a problem hiding this comment.
Pull request overview
This PR updates DRF’s ModelSerializer field generation to propagate Django model field default values into the generated serializer fields, aligning validation behavior (and metadata/schema output) more closely with model defaults.
Changes:
- Propagate
model_field.defaultinto serializer field kwargs whenmodel_field.has_default()inget_field_kwargs. - Surface non-callable field defaults in
SimpleMetadata.get_field_info(for OPTIONS metadata), consistent with OpenAPI schema behavior. - Update tests and documentation to reflect and validate default propagation behavior (including callable defaults and choices fields).
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
rest_framework/utils/field_mapping.py |
Adds default propagation for non-relational model fields via get_field_kwargs. |
rest_framework/metadata.py |
Includes non-callable field defaults in OPTIONS metadata output. |
tests/test_model_serializer.py |
Updates repr expectations and adds tests covering default propagation, callable defaults, and choices-with-default behavior. |
tests/test_metadata.py |
Adds coverage ensuring metadata includes non-callable defaults and excludes callable defaults. |
tests/schemas/test_openapi.py |
Adds schema coverage verifying model defaults appear in OpenAPI output (excluding callable defaults). |
docs/api-guide/fields.md |
Documents that ModelSerializer will use model field defaults as serializer defaults. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if model_field.has_default() or model_field.blank or model_field.null: | ||
| kwargs['required'] = False | ||
|
|
||
| if model_field.has_default(): | ||
| kwargs['default'] = model_field.default | ||
|
|
There was a problem hiding this comment.
@M2004GV please cross check this suggestion and others relevant ones
There was a problem hiding this comment.
@auvipy, cross-checked both suggestions. Relational defaults are intentionally not propagated, a FK model default is a PK value, while a serializer field default goes into validated_data as is (no to_internal_value), so propagating it would break .create(). Documented this scope in the docs and locked it in with a test. The full-update test from your autofix is kept as is (plus a missing blank line fix for flake8).
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Description
Refreshed resubmission of #8130, which was closed without being merged.
ModelSerializeralready propagates a model field'sdefaultto the generated serializer field when that field participates in a uniqueness constraint (get_uniqueness_extra_kwargs), but not otherwise. This means a plainIntegerField(default=0)(with no uniqueness constraint) is generated asrequired=Falsebut with nodefault, so omitting it from the input simply drops the attribute instead of falling back to the model's default — inconsistent with how Django'sModelFormbehaves, and surprising given DRF already does the equivalent for unique-constrained fields.This PR generalizes that existing behavior: whenever
model_field.has_default(), the value is passed through as the serializer field'sdefaultinget_field_kwargs.Closes #7469. Related to #2683 / #7489.
Addressing prior review feedback
On the original PR, @auvipy asked for two things before it could be accepted:
OPTIONSresponses to include the default, consistent with howdefaultis already surfaced inschemas/openapi.py. Done in the second commit:SimpleMetadata.get_field_infonow includes adefaultkey for any non-callable field default.Behavior change
The only user-visible behavior change is for non-partial updates/creates: a field with a model-level
defaultthat is omitted from the input will now have that default appear invalidated_data, matching what already happens for fields covered by uniqueness constraints. Partial updates (partial=True) are unaffected — defaults are still skipped in that case, per existingField.get_default()behavior.Test plan
test_default_value_used_when_field_omittedandtest_default_value_skipped_on_partial_updateintests/test_model_serializer.py, and updated therepr()regexes intest_regular_fields/test_field_optionsto reflect the newdefault=...kwarg.test_default_value_field_infoandtest_callable_default_field_infointests/test_metadata.py.docs/api-guide/fields.mddocumenting the propagation.flake8clean on changed files.