Skip to content

Model Validation

StateZero provides client-side validation that leverages your server-side serializers as the source of truth. This approach ensures consistency between frontend validation and backend data integrity.

Philosophy

We favor server-side validation because:

  • Serializers are the source of truth - Your Django REST Framework serializers already define validation rules
  • Consistency - Frontend and backend use the same validation logic
  • Security - Server-side validation cannot be bypassed by malicious clients
  • Maintainability - One place to define and update validation rules

Performance

Validation is fast because it performs no database operations. It only:

  • Checks users basic permissions (not model instance specific permissions)
  • Validates data structure using your existing serializers
  • Returns validation results without side effects

API

javascript
const instance = new Model(data)
await instance.validate(validateType, partial)

Parameters

validateType: 'create' or 'update'

  • Different validation rules may apply
  • Permission checks vary by operation type
  • Defaults to auto-detection based on whether instance has a primary key

partial: boolean

  • false - Validate all required fields (full validation)
  • true - Validate only provided fields (field-level validation)
  • Defaults to false

How It Works

The validation process automatically:

  1. Serializes your data - Converts FileObjects to paths, formats dates, handles relationships
  2. Sends to validation endpoint - Uses your existing Django serializer validation
  3. Returns results - Throws errors for invalid data, returns true for valid data
javascript
// ✅ Handles all field types automatically
const formData = {
  title: 'My Document',
  due_date: new Date(),
  attachment: fileObject,  // FileObject instance
  priority: 1
}

const todo = new Todo(formData)
await todo.validate('create', false)  // Automatically serializes everything

Use Cases

Form Validation

javascript
const handleSubmit = async (formData) => {
  try {
    const tempTodo = new Todo(formData)
    await tempTodo.validate('create')
    // Validation passed, proceed with save
  } catch (error) {
    // Handle validation errors
  }
}

Field-Level Validation

javascript
const validateField = async (fieldName, fieldValue) => {
  try {
    const tempTodo = new Todo({ [fieldName]: fieldValue })
    await tempTodo.validate('create', true)  // partial validation
    // Field is valid
  } catch (error) {
    // Show field error
  }
}

Pre-Save Validation

javascript
const todo = new Todo({
  title: 'Important Task',
  attachment: uploadedFile
})

// Validate before saving
await todo.validate()  // Auto-detects create vs update
await todo.save()      // Proceed with confidence

Error Handling

Validation throws standard StateZero exceptions:

ValidationError

javascript
try {
  const todo = new Todo(formData)
  await todo.validate('create')
} catch (error) {
  if (error instanceof ValidationError) {
    // Field-specific validation errors
    console.log(error.detail)  // { field: ['Error message'], ... }
    
    // Example: { title: ['This field is required'], priority: ['Invalid choice'] }
  }
}

PermissionDenied

javascript
try {
  await todo.validate('create')
} catch (error) {
  if (error instanceof PermissionDenied) {
    // User doesn't have permission to create this model
    console.log(error.message)
  }
}

Common Patterns

Vue.js Debounced Field Validation

javascript
import { debounce } from 'lodash'

const debouncedValidate = debounce(async (fieldName, fieldValue) => {
  try {
    const tempTodo = new Todo({ [fieldName]: fieldValue })
    await tempTodo.validate('create', true)
    // Clear any existing field error
    delete fieldErrors[fieldName]
  } catch (error) {
    if (error.detail?.[fieldName]) {
      fieldErrors[fieldName] = error.detail[fieldName][0]
    }
  }
}, 200)

Global Form Validation

javascript
const validateForm = async (formData) => {
  try {
    const tempTodo = new Todo(formData)
    await tempTodo.validate('create', false)  // Full validation
    return { valid: true }
  } catch (error) {
    return { 
      valid: false, 
      fieldErrors: error.detail || {},
      generalError: error.message 
    }
  }
}

Best Practices

  1. Create temporary instances - Don't worry about performance, they're lightweight
  2. Use partial validation for individual fields - Set partial: true for field-level checks
  3. Handle both field and general errors - Check error.detail for field errors, error.message for general issues
  4. Validate before save - Catch issues early in your user interface
  5. Debounce field validation - Avoid excessive API calls during typing

Tips

  • FileObjects are handled automatically - No need to manually convert to paths
  • Dates are serialized properly - JavaScript Date objects work seamlessly
  • Relationships are resolved - Related model instances are converted to primary keys
  • Empty fields are normalized - Empty strings become null for optional fields as needed