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):

  1. ZoneComplianceSerializer - Zone compliance configuration
  2. ZoneMerchandisingSerializer - Zone merchandising standards
  3. ParLevelItemSerializer - Par level assignments with validation
  4. ZoneInventorySerializer - Zone inventory configuration
  5. ZoneInventoryDetailSerializer - Extended inventory with counts
  6. ZoneSerializer - Main zone with nested zone types
  7. PlanogramProductSerializer - Planogram product assignments
  8. PlanogramSerializer - Planogram with nested data
  9. ZoneSimpleSerializer - Simplified zone for nested use
  10. ParLevelItemCreateSerializer - Create par levels with validation
  11. 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

FieldTypeRead-OnlyDescription
idUUIDYesCompliance record identifier
sanity_idStringNoSanity CMS identifier
checklist_itemsArrayNoList of compliance checklist items
requires_photoBooleanNoWhether photo evidence is required
frequencyStringNoCompliance check frequency
created_atDateTimeYesRecord creation timestamp
updated_atDateTimeYesLast 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

FieldTypeRead-OnlyDescription
idUUIDYesMerchandising record identifier
sanity_idStringNoSanity CMS identifier
display_standardsArrayNoList of display standard items
required_products_countIntegerYesComputed count of required products
created_atDateTimeYesRecord creation timestamp
updated_atDateTimeYesLast 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

FieldTypeRead-OnlyDescription
idUUIDYesPar level item identifier
sanity_idStringNoSanity CMS identifier
productFKNoReference to Product
product_nameStringYesProduct name from relationship
variant_skuStringNoProduct variant SKU
quantityIntegerNoPar level quantity
variant_existsBooleanYesWhether variant SKU is valid
inventory_level_existsBooleanYesWhether InventoryLevel exists
created_atDateTimeYesRecord creation timestamp
updated_atDateTimeYesLast 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 True if ProductVariant with matching SKU exists
  • Returns False if 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 True if inventory tracking infrastructure exists
  • Returns False if 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

FieldTypeRead-OnlyDescription
idUUIDYesInventory record identifier
sanity_idStringYesSanity CMS identifier
par_levelsArrayYesNested ParLevelItem objects
count_frequencyStringNoInventory count frequency
last_count_dateDateNoLast inventory count date
created_atDateTimeYesRecord creation timestamp
updated_atDateTimeYesLast 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:

FieldTypeRead-OnlyDescription
par_levels_countIntegerYesComputed 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:

  1. PlanogramProducts - Product assignments with quantities and positioning
  2. Target Stores - Which stores should use this planogram
  3. 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:

  1. Variant SKU exists for the product
  2. No duplicate par levels for same zone/product/variant

Use Case: API endpoint for manually creating par levels

PlanogramProductCreateSerializer

Validates:

  1. Variant SKU exists for the product
  2. No duplicate products in same planogram

Use Case: API endpoint for manually adding products to planograms


Zones Domain

Related Domains

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.

Was this page helpful?