from rest_framework import serializers from authentication.models import KnownIdentity, ToolshedUser, FriendRequestIncoming from authentication.serializers import OwnerSerializer from files.models import File from files.serializers import FileSerializer from toolshed.models import Category, Property, ItemProperty, InventoryItem, Tag, StorageLocation, WorkflowInstance def parse_handle(handle): """Parse a fully qualified handle like 'git:base#property:length' into (origin, entity_type, name)""" if '#' not in handle: # Fallback to old format (just name) return None, None, handle origin, rest = handle.split('#', 1) if ':' not in rest: return origin, None, rest entity_type, name = rest.split(':', 1) return origin, entity_type, name def resolve_category_handle(handle): """Resolve a fully qualified handle to a Category object""" origin, entity_type, name = parse_handle(handle) if origin and entity_type == 'category': return Category.objects.get(origin=origin, name=name) # Fallback to name-only lookup return Category.objects.get(name=handle.split('/')[-1]) def resolve_property_handle(handle): """Resolve a fully qualified handle to a Property object""" origin, entity_type, name = parse_handle(handle) if origin and entity_type == 'property': return Property.objects.get(origin=origin, name=name) # Fallback to name-only lookup return Property.objects.get(name=handle) def resolve_tag_handle(handle): """Resolve a fully qualified handle to a Tag object""" origin, entity_type, name = parse_handle(handle) if origin and entity_type == 'tag': return Tag.objects.get(origin=origin, name=name) # Fallback to name-only lookup return Tag.objects.get(name=handle) class FriendSerializer(serializers.ModelSerializer): username = serializers.SerializerMethodField() class Meta: model = KnownIdentity fields = ['id', 'username', 'public_key'] def get_username(self, obj): return obj.username + '@' + obj.domain class FriendRequestSerializer(serializers.ModelSerializer): befriender = serializers.SerializerMethodField() class Meta: model = FriendRequestIncoming fields = ['befriender', 'befriender_public_key', 'secret', 'id'] def get_befriender(self, obj): return obj.befriender_username + '@' + obj.befriender_domain class PropertySerializer(serializers.ModelSerializer): category = serializers.SerializerMethodField() handle = serializers.SerializerMethodField() def get_category(self, obj): return resolve_category_handle(obj.category.get_handle()) if obj.category else None def get_handle(self, obj): return obj.get_handle() class Meta: model = Property fields = ['name', 'description', 'category', 'unit_symbol', 'unit_name', 'unit_name_plural', 'base2_prefix', 'handle'] class CategorySerializer(serializers.ModelSerializer): handle = serializers.SerializerMethodField() class Meta: model = Category fields = ['name', 'handle'] def get_handle(self, obj): return obj.get_handle() def to_representation(self, instance): return instance.name def to_internal_value(self, data): return resolve_category_handle(data.split("/")[-1]) class StorageLocationSerializer(serializers.ModelSerializer): owner = OwnerSerializer(read_only=True) category = serializers.CharField(required=False, allow_null=True, allow_blank=True) path = serializers.SerializerMethodField() class Meta: model = StorageLocation fields = ['id', 'name', 'description', 'path', 'category', 'owner', 'parent'] read_only_fields = ['path'] @staticmethod def get_path(obj): if obj.parent: return StorageLocationSerializer.get_path(obj.parent) + "/" + obj.name return obj.name class ItemPropertySerializer(serializers.ModelSerializer): property = PropertySerializer(read_only=True) handle = serializers.SerializerMethodField() class Meta: model = ItemProperty fields = ['property', 'value', 'handle'] def get_handle(self, obj): return obj.property.get_handle() def to_representation(self, instance): return {'value': instance.value, 'name': instance.property.name} def to_internal_value(self, data): prop = resolve_property_handle(data.get('name') or data.get('handle')) value = data['value'] return {'property': prop, 'value': value} class InventoryItemSerializer(serializers.ModelSerializer): owner = OwnerSerializer(read_only=True) tags = serializers.SerializerMethodField() tags_input = serializers.ListField(child=serializers.CharField(), write_only=True, required=False) properties = ItemPropertySerializer(many=True, required=False, source='itemproperty_set') category = CategorySerializer(required=False, allow_null=True) files = FileSerializer(many=True, read_only=True) class Meta: model = InventoryItem fields = ['id', 'name', 'description', 'owner', 'category', 'availability_policy', 'owned_quantity', 'owner', 'tags', 'tags_input', 'properties', 'files', 'storage_location'] def get_tags(self, obj): return [tag.name for tag in obj.tags.all()] def to_internal_value(self, data): files = data.pop('files', []) tags_input = data.pop('tags_input', data.pop('tags', [])) ret = super().to_internal_value(data) ret['tags'] = [resolve_tag_handle(tag) for tag in tags_input] ret['files'] = files return ret def create(self, validated_data): tags = validated_data.pop('tags', []) props = validated_data.pop('itemproperty_set', []) files = validated_data.pop('files', []) item = InventoryItem.objects.create(**validated_data) for tag in tags: item.tags.add(tag, through_defaults={}) for prop in props: ItemProperty.objects.create(inventory_item=item, property=prop['property'], value=prop['value']) for file in files: if type(file) == dict: file_serializer = FileSerializer(data=file) if file_serializer.is_valid(): file_serializer.save() item.files.add(file_serializer.instance) else: raise serializers.ValidationError(file_serializer.errors) elif type(file) == int: if File.objects.filter(id=file).exists(): item.files.add(File.objects.get(id=file)) else: raise serializers.ValidationError("File with id {} does not exist".format(file)) item.save() return item def update(self, instance, validated_data): tags = validated_data.pop('tags', []) props = validated_data.pop('itemproperty_set', []) item = super().update(instance, validated_data) item.tags.clear() item.properties.clear() if 'category' not in validated_data: item.category = None for tag in tags: item.tags.add(tag) for prop in props: ItemProperty.objects.create(inventory_item=item, property=prop['property'], value=prop['value']) item.save() return item class WorkflowInstanceSerializer(serializers.ModelSerializer): owner = serializers.StringRelatedField(read_only=True) class Meta: model = WorkflowInstance fields = ['id', 'slug', 'state', 'payload', 'owner', 'created_at', 'updated_at'] read_only_fields = ['owner', 'created_at', 'updated_at']