toolshed/backend/toolshed/serializers.py
2026-08-01 12:26:18 +02:00

211 lines
7.7 KiB
Python

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.get_handle()
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, 'handle': instance.property.get_handle()}
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.get_handle() 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', 'name', 'state', 'payload', 'owner', 'created_at', 'updated_at']
read_only_fields = ['owner', 'created_at', 'updated_at']