import logging
from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from taggit_serializer.serializers import TaggitSerializer, TagListSerializerField
from fiction_outlines.models import Series, Character, Location, Outline
from fiction_outlines.models import CharacterInstance, LocationInstance, Arc
from fiction_outlines.models import ArcElementNode, StoryElementNode, MACE_TYPES
logger = logging.getLogger('fiction-outlines-api')
MOVE_POSITION_CHOICES = (
('first-child', 'first-child'),
('last-child', 'last-child'),
('first-sibling', 'first-sibling'),
('last-sibling', 'last-sibling'),
('left', 'left'),
('right', 'right'),
)
[docs]def convert_annotated_list(annotated_list, serializer_class):
'''
Takes an annotated list and a serializer class and returns a version that is suitable for
serialization.
'''
converted_list = [] # pragma: no cover
for item, info in annotated_list:
converted_list.append((serializer_class(item).data, info))
return converted_list
[docs]class SeriesSerializer(TaggitSerializer, serializers.ModelSerializer):
'''
Serializer for Series model.
'''
tags = TagListSerializerField()
[docs]class CharacterInstanceSerializer(serializers.ModelSerializer):
'''
Serializer for character instance.
'''
name = serializers.CharField(read_only=True, source='character.name')
[docs]class LocationInstanceSerializer(serializers.ModelSerializer):
'''
Serializer for location instance.
'''
name = serializers.CharField(read_only=True, source='location.name')
[docs]class CharacterSerializer(TaggitSerializer, serializers.ModelSerializer):
'''
Serializer for Character model.
'''
tags = TagListSerializerField()
series = serializers.PrimaryKeyRelatedField(many=True, allow_null=True, queryset=Series.objects.all())
character_instances = CharacterInstanceSerializer(many=True, read_only=True,
required=False, source='characterinstance_set')
[docs]class LocationSerializer(TaggitSerializer, serializers.ModelSerializer):
'''
Serializer for Location model.
'''
tags = TagListSerializerField()
series = serializers.PrimaryKeyRelatedField(many=True, allow_null=True, queryset=Series.objects.all())
location_instances = LocationInstanceSerializer(many=True, read_only=True,
required=False, source='locationinstance_set')
[docs]class OutlineSerializer(TaggitSerializer, serializers.ModelSerializer):
'''
Serializer for Outline model.
Also provides ``outline_structure``, which is a treebeard annotated list of story element nodes.
Special read-only fields:
:attribute length_estimate:
Auto calculated length (in words) based on the number of characters, locations, and arcs.
:attribute arc_set:
The :class:`fiction_outlines.models.Arc` objects associated with this outline.
:attribute characterinstance_set:
Character instances associated with this outline.
:attribute locationinstance_set:
Location instances associated with this outline.
:attribute outline_structure:
An annotated list of :class:`fiction_outlines.models.StoryElementNode` objects for visualizing the
tree structure.
'''
length_estimate = serializers.IntegerField(read_only=True,
required=False,
help_text=_("Autogenerated length estimate for the story in number of words.")) # noqa: E501
tags = TagListSerializerField()
series = serializers.PrimaryKeyRelatedField(allow_null=True, queryset=Series.objects.all())
arc_set = serializers.PrimaryKeyRelatedField(many=True, required=False, read_only=True)
characterinstance_set = serializers.PrimaryKeyRelatedField(many=True, required=False, read_only=True)
locationinstance_set = serializers.PrimaryKeyRelatedField(many=True, required=False, read_only=True)
outline_structure = serializers.SerializerMethodField(read_only=True,
help_text=_("Annotated list of outline items."))
[docs] def get_outline_structure(self, obj):
'''
Retrieves all :class:`fiction_outlines.StoryElementNode` objects associated with this outline,
and returns it as an annotated list that can be serialized.
:param obj:
The outline object this serializer represents.
:returns: An annotated list for serialization or an empty list.
'''
logger.debug("Checking outline structure.")
if obj.story_tree_root:
logger.debug('Found a root node')
annotated_list = StoryElementNode.get_annotated_list(obj.story_tree_root)
logger.debug('Annotated list retrieved.')
if len(annotated_list) > 1:
# There is more than just a single root node: serialize and return.
annotated_list = convert_annotated_list(annotated_list, StoryElementNodeSerializer)
logger.debug("Returning results to field.") # pragma: no cover
return annotated_list[1:]
return []
[docs]class ArcSerializer(serializers.ModelSerializer):
'''
Serializer for Arc model.
To create an Arc, make sure to instead use the :class:`ArcCreateSerializer` and
post it to :class:`fiction_outlines_api.views.ArcCreateView`
'''
outline = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
current_errors = serializers.ReadOnlyField(read_only=True, required=False,
help_text=_('dict current validation errors'))
arc_structure = serializers.SerializerMethodField(read_only=True, help_text=_('Annotated list of arc elements.'))
[docs] def get_arc_structure(self, obj):
'''
Fetches all the :class:`fiction_outlines.models.ArcElementNode` objects assocaited with this arc as an
annotated list.
:param obj:
The arc this serializer represents.
:returns: An annotated list ready to be serialized or an empty list.
'''
logger.debug("Attempting to retrieve arc structure for arc %s" % obj.pk)
if obj.arc_root_node:
annotated_list = ArcElementNode.get_annotated_list(obj.arc_root_node)
logger.debug("Retrieved annotated list")
if len(annotated_list) > 1:
# There is more than just a root node which we don't want to display to users.
logger.debug("Arc structure found, builidng serialized structure.")
annotated_list = convert_annotated_list(annotated_list, ArcElementNodeSerializer)
logger.debug("Success serializing arc!")
logger.debug("Arc looks like this:\n\n%s" % annotated_list[1:])
logger.debug("Returning result to field.")
return annotated_list[1:]
else:
logger.debug("Only a root node... returning empty list.")
return [] # pragma: no cover This is really unlikely to occur.
[docs]class ArcCreateSerializer(serializers.Serializer):
'''
Serializer used for arc creation. You can't create an Arc using it's primary serializer.
'''
mace_type = serializers.ChoiceField(choices=MACE_TYPES)
name = serializers.CharField(max_length=255)
[docs]class ArcElementNodeSerializer(serializers.ModelSerializer):
'''
Serializer for ArcElementNode.
Special read-only attributes:
:attribute is_milestone:
Does this element represent a milestone in the :class:`Arc`
:attribute milestone_seq:
If a milestone, what is the proscribed sequence where it should appear
in the context of the other milestones.
:attribute parent_outline:
Convenience reference to the parent outline object.
:attribute headline:
Automatically generated from the first line of the description.
'''
arc = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
headline = serializers.ReadOnlyField(required=False, help_text=_('Auto generated from description'))
story_element_node = serializers.PrimaryKeyRelatedField(required=False, allow_null=True,
queryset=StoryElementNode.objects.all())
parent_outline = serializers.PrimaryKeyRelatedField(source='arc.outline', read_only=True, required=False)
milestone_seq = serializers.IntegerField(allow_null=True, read_only=True, required=False,
help_text=_('If a milestone, what order *should* it appear in?'))
[docs]class StoryElementNodeSerializer(serializers.ModelSerializer):
'''
Serializer for StoryElementNode
Two special fields here:
:attribute all_characters:
All characters associated with this node or its descendants.
:attribute all_locations:
All locations associated with this node of its descendants.
'''
all_characters = CharacterInstanceSerializer(many=True, read_only=True, required=False,
help_text=_('All characters associated with this node its descendants.')) # noqa: E501
all_locations = LocationInstanceSerializer(many=True, read_only=True, required=False,
help_text=_('All locations associated with this node or its descendants.')) # noqa: E501
outline = serializers.PrimaryKeyRelatedField(read_only=True, required=False)
[docs] class Meta: # pragma: no cover
model = StoryElementNode
fields = ('id', 'story_element_type', 'name', 'description', 'outline', 'assoc_characters',
'assoc_locations', 'all_characters', 'all_locations', 'impact_rating')
read_only_fields = ('all_characters', 'all_locations', 'impact_rating', 'outline', 'id')