You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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 country field was never set (original value is None), we allow leaving it unset or setting a valid country code.
  • If the user's country field was already set (original value is a valid code), we cannot allow clearing it (i.e., setting it to None or 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=True when instantiating the schema. The validator will only run if the country field 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

火山引擎 最新活动