import logging import pathlib from functools import cached_property import yaml from core.utils.db import fast_first from rest_framework import serializers from .models import ProductTourInteractionData, ProductTourState, UserProductTour logger = logging.getLogger(__name__) PRODUCT_TOURS_CONFIGS_DIR = pathlib.Path(__file__).parent / 'configs' class UserProductTourSerializer(serializers.ModelSerializer): # steps is a list of steps in the tour loaded from the yaml file steps = serializers.SerializerMethodField(read_only=True) # awaiting is a boolean that indicates if the tour is awaiting other tours in the list of "dependencies" awaiting = serializers.SerializerMethodField(read_only=True) class Meta: model = UserProductTour fields = '__all__' @cached_property def available_tours(self): return {pathlib.Path(f).stem for f in PRODUCT_TOURS_CONFIGS_DIR.iterdir()} def validate_name(self, value): if value not in self.available_tours: raise serializers.ValidationError( f'Product tour {value} not found. Available tours: {self.available_tours}' ) return value @cached_property def load_tour_config(self): # TODO: get product tour from yaml file. Later we move it to remote storage, e.g. S3 filepath = PRODUCT_TOURS_CONFIGS_DIR / f'{self.context["name"]}.yml' with open(filepath, 'r') as f: return yaml.safe_load(f) def get_awaiting(self, obj): config = self.load_tour_config dependencies = config.get('dependencies', []) for dependency in dependencies: tour = fast_first(UserProductTour.objects.filter(user=self.context['request'].user, name=dependency)) if not tour or tour.state != ProductTourState.COMPLETED: logger.info(f'Tour {dependency} is not completed: skipping tour {self.context["name"]}') return True return False def get_steps(self, obj): config = self.load_tour_config return config.get('steps', []) def validate_interaction_data(self, value): try: # Validate interaction data using pydantic model ProductTourInteractionData(**value) return value except Exception: raise serializers.ValidationError('Invalid product tour interaction data format.')