Zones Serializers - Data Validation
Location: api/nextango/apps/zones/serializers.py
Last Updated: 2025-12-21
Overview
The zones serializers module provides Django REST Framework serializers for zone management, merchandising, compliance, and inventory tracking. These serializers handle Sanity webhook data transformation, nested relationship management, and data validation for the zones domain.
Key Features:
- Sanity webhook processing: Converts camelCase to snake_case field names
- Nested relationship handling: Zone types, par levels, planogram products
- SKU validation: Ensures variant SKUs match actual ProductVariant records
- Computed fields: SerializerMethodField for counts and validation status
- Read-only design: Most serializers are read-only for webhook data
Serializers (11 total):
- ZoneComplianceSerializer - Zone compliance configuration
- ZoneMerchandisingSerializer - Zone merchandising standards
- ParLevelItemSerializer - Par level assignments with validation
- ZoneInventorySerializer - Zone inventory configuration
- ZoneInventoryDetailSerializer - Extended inventory with counts
- ZoneSerializer - Main zone with nested zone types
- PlanogramProductSerializer - Planogram product assignments
- PlanogramSerializer - Planogram with nested data
- ZoneSimpleSerializer - Simplified zone for nested use
- ParLevelItemCreateSerializer - Create par levels with validation
- PlanogramProductCreateSerializer - Create planogram products with validation
ZoneComplianceSerializer
Basic serializer for zone compliance configuration.
Purpose
Serializes ZoneCompliance records for zones that require compliance checking (checklists, photos, frequency).
Fields
| Field | Type | Read-Only | Description |
|---|---|---|---|
id | UUID | Yes | Compliance record identifier |
sanity_id | String | No | Sanity CMS identifier |
checklist_items | Array | No | List of compliance checklist items |
requires_photo | Boolean | No | Whether photo evidence is required |
frequency | String | No | Compliance check frequency |
created_at | DateTime | Yes | Record creation timestamp |
updated_at | DateTime | Yes | Last update timestamp |
Configuration
class ZoneComplianceSerializer(serializers.ModelSerializer):
"""Serializer for ZoneCompliance model."""
class Meta:
model = ZoneCompliance
fields = [
'id', 'sanity_id', 'checklist_items', 'requires_photo', 'frequency',
'created_at', 'updated_at'
]
read_only_fields = ['id', 'created_at', 'updated_at']
Response Example
{
"id": "uuid-123",
"sanity_id": "compliance-abc",
"checklist_items": [
"Verify zone cleanliness",
"Check product placement",
"Ensure proper lighting"
],
"requires_photo": true,
"frequency": "daily",
"created_at": "2025-11-18T10:00:00Z",
"updated_at": "2025-11-18T10:00:00Z"
}
ZoneMerchandisingSerializer
Serializer for zone merchandising standards with required product counts.
Purpose
Serializes ZoneMerchandising records for zones that have display standards and required product types.
Fields
| Field | Type | Read-Only | Description |
|---|---|---|---|
id | UUID | Yes | Merchandising record identifier |
sanity_id | String | No | Sanity CMS identifier |
display_standards | Array | No | List of display standard items |
required_products_count | Integer | Yes | Computed count of required products |
created_at | DateTime | Yes | Record creation timestamp |
updated_at | DateTime | Yes | Last update timestamp |
Configuration
class ZoneMerchandisingSerializer(serializers.ModelSerializer):
"""Serializer for ZoneMerchandising model."""
required_products_count = serializers.SerializerMethodField()
class Meta:
model = ZoneMerchandising
fields = [
'id', 'sanity_id', 'display_standards', 'required_products_count',
'created_at', 'updated_at'
]
read_only_fields = ['id', 'created_at', 'updated_at', 'required_products_count']
def get_required_products_count(self, obj):
return obj.required_products.count()
Response Example
{
"id": "uuid-456",
"sanity_id": "merchandising-def",
"display_standards": [
"Products at eye level",
"FIFO rotation",
"Price tags visible"
],
"required_products_count": 5,
"created_at": "2025-11-18T10:00:00Z",
"updated_at": "2025-11-18T10:00:00Z"
}
ParLevelItemSerializer
Serializer for par level items with variant validation and inventory level checks.
Purpose
Serializes ParLevelItem records with computed fields for SKU validation and inventory level existence checks.
Fields
| Field | Type | Read-Only | Description |
|---|---|---|---|
id | UUID | Yes | Par level item identifier |
sanity_id | String | No | Sanity CMS identifier |
product | FK | No | Reference to Product |
product_name | String | Yes | Product name from relationship |
variant_sku | String | No | Product variant SKU |
quantity | Integer | No | Par level quantity |
variant_exists | Boolean | Yes | Whether variant SKU is valid |
inventory_level_exists | Boolean | Yes | Whether InventoryLevel exists |
created_at | DateTime | Yes | Record creation timestamp |
updated_at | DateTime | Yes | Last update timestamp |
Configuration
class ParLevelItemSerializer(serializers.ModelSerializer):
"""Serializer for ParLevelItem model."""
product_name = serializers.CharField(source='product.name', read_only=True)
variant_exists = serializers.SerializerMethodField()
inventory_level_exists = serializers.SerializerMethodField()
class Meta:
model = ParLevelItem
fields = [
'id', 'sanity_id', 'product', 'product_name', 'variant_sku', 'quantity',
'variant_exists', 'inventory_level_exists', 'created_at', 'updated_at'
]
read_only_fields = ['id', 'created_at', 'updated_at', 'product_name', 'variant_exists', 'inventory_level_exists']
def get_variant_exists(self, obj):
return obj.get_product_variant() is not None
def get_inventory_level_exists(self, obj):
try:
InventoryLevel = apps.get_model('products', 'InventoryLevel')
return InventoryLevel.objects.filter(
product=obj.product,
variant_sku=obj.variant_sku,
store=obj.zone_inventory.zone.store_location
).exists()
except Exception:
return False
Computed Fields
variant_exists:
- Calls
ParLevelItem.get_product_variant() - Returns
Trueif ProductVariant with matching SKU exists - Returns
Falseif SKU is invalid or product doesn't have that variant
inventory_level_exists:
- Checks if InventoryLevel record exists for this product variant at store
- Returns
Trueif inventory tracking infrastructure exists - Returns
Falseif no InventoryLevel (infrastructure not created yet)
Response Example
{
"id": "uuid-789",
"sanity_id": "par-level-ghi",
"product": "uuid-product-123",
"product_name": "Dark Roast Coffee",
"variant_sku": "DARK-12OZ",
"quantity": 10,
"variant_exists": true,
"inventory_level_exists": true,
"created_at": "2025-11-18T10:00:00Z",
"updated_at": "2025-11-18T10:00:00Z"
}
ZoneInventorySerializer
Serializer for zone inventory configuration with nested par levels.
Purpose
Serializes ZoneInventory records with all associated par level items included.
Fields
| Field | Type | Read-Only | Description |
|---|---|---|---|
id | UUID | Yes | Inventory record identifier |
sanity_id | String | Yes | Sanity CMS identifier |
par_levels | Array | Yes | Nested ParLevelItem objects |
count_frequency | String | No | Inventory count frequency |
last_count_date | Date | No | Last inventory count date |
created_at | DateTime | Yes | Record creation timestamp |
updated_at | DateTime | Yes | Last update timestamp |
Configuration
class ZoneInventorySerializer(serializers.ModelSerializer):
"""Serializer for ZoneInventory model."""
par_levels = ParLevelItemSerializer(many=True, read_only=True)
class Meta:
model = ZoneInventory
fields = [
'id', 'sanity_id', 'par_levels', 'count_frequency', 'last_count_date',
'created_at', 'updated_at'
]
read_only_fields = ['id', 'sanity_id', 'created_at', 'updated_at']
Response Example
{
"id": "uuid-012",
"sanity_id": "inventory-jkl",
"count_frequency": "weekly",
"last_count_date": "2025-11-15",
"par_levels": [
{
"id": "uuid-789",
"product_name": "Dark Roast Coffee",
"variant_sku": "DARK-12OZ",
"quantity": 10,
"variant_exists": true,
"inventory_level_exists": true
},
{
"id": "uuid-790",
"product_name": "Light Roast Coffee",
"variant_sku": "LIGHT-12OZ",
"quantity": 8,
"variant_exists": true,
"inventory_level_exists": true
}
],
"created_at": "2025-11-18T10:00:00Z",
"updated_at": "2025-11-18T10:00:00Z"
}
ZoneInventoryDetailSerializer
Extended zone inventory serializer with additional counts.
Purpose
Provides detailed zone inventory information with par level count.
Fields
Same as ZoneInventorySerializer plus:
| Field | Type | Read-Only | Description |
|---|---|---|---|
par_levels_count | Integer | Yes | Computed count of par levels |
Configuration
class ZoneInventoryDetailSerializer(serializers.ModelSerializer):
"""Detailed serializer for ZoneInventory model with par levels."""
par_levels = ParLevelItemSerializer(many=True, read_only=True)
par_levels_count = serializers.SerializerMethodField()
class Meta:
model = ZoneInventory
fields = [
'id', 'sanity_id', 'count_frequency', 'last_count_date',
'par_levels', 'par_levels_count', 'created_at', 'updated_at'
]
read_only_fields = ['id', 'created_at', 'updated_at', 'par_levels', 'par_levels_count']
def get_par_levels_count(self, obj):
return obj.par_levels.count()
Response Example
{
"id": "uuid-012",
"sanity_id": "inventory-jkl",
"count_frequency": "weekly",
"last_count_date": "2025-11-15",
"par_levels_count": 12,
"par_levels": [ /* array of par level items */ ],
"created_at": "2025-11-18T10:00:00Z",
"updated_at": "2025-11-18T10:00:00Z"
}
ZoneSerializer
Main zone serializer with nested zone type handling and Sanity webhook processing.
Purpose
Comprehensive zone serializer that:
- Handles Sanity webhook data with camelCase to snake_case conversion
- Manages nested zone type modules (compliance, merchandising, inventory)
- Creates and updates related zone type records
- Validates zone uniqueness per store
Webhook Processing Pattern
Complete workflow:
Sanity CMS → Webhook → ZoneSerializer
↓
to_internal_value(): Convert camelCase to snake_case, resolve references
↓
validate(): Check zone_name uniqueness per store
↓
create() or update(): Save Zone instance
↓
_process_zone_type_modules(): Create nested zone type records
↓
Return Zone with nested zone_compliance/zone_merchandising/zone_inventory
Field Conversion Example
Sanity (camelCase) → Django (snake_case)
zoneName → zone_name
storeLocation → store_location
isShown → is_shown
displayOrder → display_order
zoneType → zone_type
PlanogramSerializer
Complex serializer for planograms with nested product, store, and zone relationships.
Purpose
Comprehensive planogram serializer that manages:
- Nested planogram products (which products, how many, where)
- Target stores (which stores should implement planogram)
- Target zones (which zones within stores)
- Campaign relationships
- Diagram asset URL extraction
Nested Data Processing
Three types of nested relationships:
- PlanogramProducts - Product assignments with quantities and positioning
- Target Stores - Which stores should use this planogram
- Target Zones - Which zones within stores should use this planogram
Processing Pattern:
def _process_nested_data(self, planogram, products_data, target_stores_data, target_zones_data):
"""Unified method to process all nested data."""
# Clear and recreate relationships on each webhook
if products_data:
self._sync_planogram_products(planogram, products_data)
if target_stores_data:
self._sync_planogram_target_stores(planogram, target_stores_data)
if target_zones_data:
self._sync_planogram_target_zones(planogram, target_zones_data)
Create-Specific Serializers
ParLevelItemCreateSerializer
Validates:
- Variant SKU exists for the product
- No duplicate par levels for same zone/product/variant
Use Case: API endpoint for manually creating par levels
PlanogramProductCreateSerializer
Validates:
- Variant SKU exists for the product
- No duplicate products in same planogram
Use Case: API endpoint for manually adding products to planograms
Related Documentation
Zones Domain
- README - Zones domain overview
- models.md - Zone, ParLevelItem, ZoneInventory, Planogram models
- services.md - ZoneAssignmentService business logic
- views.md - Zone API endpoints
- signals.md - Zone sync patterns
Related Domains
- products/models.md - Product, ProductVariant models
- stores/models.md - Store model
- integrations/sync/field_mappings.md - Field mapping for Sanity webhooks
DRF Documentation
Summary: Zones serializers handle Sanity webhook data transformation, nested relationship management, and comprehensive SKU validation for zone management, merchandising, compliance, and inventory tracking.