Marshmallow中如何实现字段软强制设置及禁止清除已设值?
How to Implement Conditional "Cannot Clear" Validation in Marshmallow for Edit Schemas
Great question! This is a common business rule scenario, and Marshmallow gives us the tools to handle it cleanly with custom validators and context. Let's break down the solution step by step.
Core Rule Recap
For the edit user schema:
- If the user's
countryfield was never set (original value isNone), we allow leaving it unset or setting a valid country code. - If the user's
countryfield was already set (original value is a valid code), we cannot allow clearing it (i.e., setting it toNoneor an empty value).
Implementation Steps
1. Pass the Original User Data to the Schema Context
First, when you instantiate your edit schema, pass the original user object (or its data) into Marshmallow's context dictionary. This lets the validator access the existing value of country during validation.
# Example: If your original user is a database model instance edit_schema = EditUserSchema(context={"original_user": existing_user}) # Or if you're working with a dictionary of user data edit_schema = EditUserSchema(context={"original_user": existing_user_dict})
2. Define the Edit Schema with Custom Validation
We'll create a custom field validator that checks our conditional rule, while retaining the existing OneOf validation for valid country codes.
from marshmallow import Schema, fields, ValidationError, validates from marshmallow.validate import OneOf # Assume COUNTRY_CODES is your list of valid country codes COUNTRY_CODES = ["US", "CA", "GB", ...] class EditUserSchema(Schema): # Allow None here because we'll handle the conditional restriction in our validator country = fields.String(allow_none=True) @validates("country") def validate_country_rule(self, value, **kwargs): # Fetch the original country value from the schema context original_country = self.context["original_user"].country # Adjust if using a dict: .get("country") # Rule 1: If original country was set, cannot clear it to None if original_country is not None and value is None: raise ValidationError("Country cannot be cleared once it has been set.") # Rule 2: If a new country value is provided, it must be a valid code if value is not None: # Reuse the OneOf validator to ensure validity OneOf(COUNTRY_CODES)(value)
3. How It Works
Let's test a few scenarios to confirm the rule behaves as expected:
- Case 1: Original country is set, user tries to clear it
existing_user = User(country="US") data = {"country": None} errors = EditUserSchema(context={"original_user": existing_user}).validate(data) # Errors will be: {"country": ["Country cannot be cleared once it has been set."]} - Case 2: Original country is unset, user leaves it unset
existing_user = User(country=None) data = {} # Partial update, no country field provided errors = EditUserSchema(context={"original_user": existing_user}, partial=True).validate(data) # No errors - allowed to keep country unset - Case 3: Original country is unset, user sets a valid code
existing_user = User(country=None) data = {"country": "CA"} errors = EditUserSchema(context={"original_user": existing_user}).validate(data) # No errors - valid code is accepted - Case 4: Original country is set, user updates to another valid code
existing_user = User(country="US") data = {"country": "GB"} errors = EditUserSchema(context={"original_user": existing_user}).validate(data) # No errors - updating to another valid code is allowed
Key Notes
- Partial Updates: If your edit endpoint supports partial updates (only sending changed fields), use
partial=Truewhen instantiating the schema. The validator will only run if thecountryfield is included in the submitted data, which aligns with our rule (not submitting the field means keeping the original value). - Reusable Validator: If you need this rule across multiple fields, you can extract the conditional logic into a reusable validator function instead of a field-specific method.
内容的提问来源于stack exchange,提问作者firelynx




