Skip to content

Propagate model field defaults to ModelSerializer fields - #9997

Open
M2004GV wants to merge 9 commits into
encode:mainfrom
M2004GV:propagate-model-field-defaults
Open

Propagate model field defaults to ModelSerializer fields#9997
M2004GV wants to merge 9 commits into
encode:mainfrom
M2004GV:propagate-model-field-defaults

Conversation

@M2004GV

@M2004GV M2004GV commented Jul 3, 2026

Copy link
Copy Markdown

Description

Refreshed resubmission of #8130, which was closed without being merged.

ModelSerializer already propagates a model field's default to the generated serializer field when that field participates in a uniqueness constraint (get_uniqueness_extra_kwargs), but not otherwise. This means a plain IntegerField(default=0) (with no uniqueness constraint) is generated as required=False but with no default, so omitting it from the input simply drops the attribute instead of falling back to the model's default — inconsistent with how Django's ModelForm behaves, 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's default in get_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:

  1. Rebase with a fresh approach — done here as a clean two-commit PR.
  2. Update OPTIONS responses to include the default, consistent with how default is already surfaced in schemas/openapi.py. Done in the second commit: SimpleMetadata.get_field_info now includes a default key 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 default that is omitted from the input will now have that default appear in validated_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 existing Field.get_default() behavior.

Test plan

  • Added test_default_value_used_when_field_omitted and test_default_value_skipped_on_partial_update in tests/test_model_serializer.py, and updated the repr() regexes in test_regular_fields / test_field_options to reflect the new default=... kwarg.
  • Added test_default_value_field_info and test_callable_default_field_info in tests/test_metadata.py.
  • Added a note to docs/api-guide/fields.md documenting the propagation.
  • Full test suite passes against both Django 5.2 and Django main (6.2 dev): 1562 passed, 0 failed.
  • flake8 clean on changed files.

@Shrikantgiri25 Shrikantgiri25 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! Quick suggestion: Add tests for:

  1. Callable defaults (uuid.uuid4) - ensure they execute correctly
  2. Choice fields with default - validates the original #7469 fix end-to-end
  3. OpenAPI schema (optional) - verify defaults appear in docs

These cover the exact scenarios from the linked issues.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.default into serializer field kwargs when model_field.has_default() in get_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.

Comment thread rest_framework/utils/field_mapping.py
Comment thread tests/test_model_serializer.py
@auvipy
auvipy requested a review from browniebroke September 5, 2026 11:00
@auvipy auvipy added the Bug label Sep 7, 2026
@auvipy auvipy added this to the 3.18 milestone Sep 7, 2026
@auvipy
auvipy requested a balanced review from Copilot September 7, 2026 06:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Relational defaults can cause invalid assignments, and explicit required=True can prevent serializer construction.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

rest_framework/utils/field_mapping.py:137

  • Primary-key OneToOneFields also pass through this helper before build_standard_field() converts them to PrimaryKeyRelatedField (serializers.py:1298-1301). With this change, an omitted relational default such as default=1 bypasses to_internal_value(), leaving the integer in validated_data; the default create/update path then assigns it to the relation descriptor instead of a model instance and fails. Exclude relational fields here, as the documentation promises.
    if model_field.has_default():
        kwargs['default'] = model_field.default
  • Files reviewed: 6/6 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment on lines +136 to +137
if model_field.has_default():
kwargs['default'] = model_field.default

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@M2004GV please cross check and fix this

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

Labels

Development

Successfully merging this pull request may close these issues.

ModelSerializer generate ChoicesField will discard default parameter

4 participants