Source code for fiction_outlines.forms
import logging
from django import forms
from django.utils.translation import ugettext_lazy as _
from treebeard import forms as tforms
from .models import CharacterInstance, LocationInstance, Outline
from .models import Character, Location, Series, ArcElementNode, StoryElementNode
logger = logging.getLogger('forms')
logger.setLevel(logging.DEBUG)
[docs]class OutlineMoveNodeForm(tforms.MoveNodeForm):
'''
Subclass of base ``treebeard`` move node form allowing us to restrict
target node options to within a single tree.
'''
__position_choices_sorted = (
('sorted-child', _('Child of')),
('sorted-sibling', _('Sibling of')),
)
__position_choices_unsorted = (
('first-child', _('First child of')),
('left', _('Before')),
('right', _('After')),
)
_position = forms.ChoiceField(required=True, label=_("Position"))
_ref_node_id = forms.TypedChoiceField(required=True,
label=_("Relative to"))
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=forms.models.ErrorList, label_suffix=':',
empty_permitted=False, instance=None, **kwargs):
'''
Here's the deal. py:module:`treebeard` has a great API with one exception.
That exception is this form. If you want to limit the tree, you need to override
the py:method:`treebeard.forms.MoveNodeForm.__init__` method, but because it does some
black magic, you can't really ever call ``super()`` for it. Which unfortunately means
that we have to embed the whole method in our class with a few small changes.
'''
root_node = kwargs.pop('root_node')
if isinstance(root_node, ArcElementNode):
type = 'ArcElementNode'
elif isinstance(root_node, StoryElementNode):
type = 'StoryElementNode'
else:
type = 'Unknown'
logger.debug("OutlineMoveNodeForm was fed a root node of type: %s - pk %s" % (type, root_node.pk))
if not root_node:
raise ValueError(_('A root_node must be specified.'))
# Beginning ``treebeard`` boilerplate.
opts = self._meta
if opts.model is None:
raise ValueError('forms.ModelForm has no class specified.')
# Update the position choice field.
self.is_sorted = getattr(opts.model, 'node_order_by', False)
if self.is_sorted:
choices_sort_mode = self.__class__.__position_choices_sorted
else:
choices_sort_mode = self.__class__.__position_choices_unsorted
self.declared_fields['_position'].choices = choices_sort_mode
# Here's where things get different as we need to ensure we only call our altered methods
# Update _ref_node_id choices
choices = self.__class__.mk_dropdown_tree(opts.model, root_node=root_node, for_node=instance)
logger.debug("Length of choices is %d" % len(choices))
self.declared_fields['_ref_node_id'].choices = choices
# More ``treebeard`` boilerplate.
# Put initial data data for fields into a map, update map with initial data,
# and pass this to the constructor.
if instance is None:
initial_ = {}
else:
initial_ = self._get_position_ref_node(instance)
if initial is not None:
initial_.update(initial)
forms.ModelForm.__init__(self, data, files, auto_id, prefix, initial_, error_class, label_suffix,
empty_permitted, instance, **kwargs)
@classmethod
def mk_dropdown_tree(cls, model, root_node, for_node=None):
'''
Override of ``treebeard`` method to enforce the same root.
'''
options = []
# The difference is that we only generate the subtree for the current root.
logger.debug("Using root node pk of %s" % root_node.pk)
cls.add_subtree(for_node, root_node, options)
return options[1:]
[docs]class CharacterInstanceForm(forms.ModelForm):
'''
Form for creating character instances
'''
def __init__(self, *args, **kwargs):
character = kwargs.pop('character', None)
super().__init__(*args, **kwargs)
if character:
self.fields['outline'].queryset = Outline.objects.filter(
user=character.user)
class Meta:
model = CharacterInstance
fields = (
'outline',
'main_character',
'pov_character',
'protagonist',
'antagonist',
'obstacle',
'villain',
)
[docs]class LocationInstanceForm(forms.ModelForm):
'''
Form for creating location instances.
'''
def __init__(self, *args, **kwargs):
location = kwargs.pop('location', None)
super().__init__(*args, **kwargs)
if location:
self.fields['outline'].queryset = Outline.objects.filter(user=location.user)
class Meta:
model = LocationInstance
fields = {
'outline',
}
[docs]class CharacterForm(forms.ModelForm):
'''
Form for Character model.
'''
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
if user:
self.fields['series'].queryset = Series.objects.filter(user=user)
else:
raise KeyError(_('Form must be instantiated with a user object.')) # pragma: no cover
class Meta:
model = Character
fields = (
'name',
'description',
'series',
'tags',
)
[docs]class LocationForm(forms.ModelForm):
'''
Form class for Locations
'''
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
if user:
self.fields['series'].queryset = Series.objects.filter(user=user)
class Meta:
model = Location
fields = (
'name',
'description',
'series',
'tags',
)
[docs]class OutlineForm(forms.ModelForm):
'''
Form class for Outline model
'''
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super().__init__(*args, **kwargs)
if user:
self.fields['series'].queryset = Series.objects.filter(user=user)
class Meta:
model = Outline
fields = (
'title',
'description',
'series',
'tags',
)
class ArcNodeForm(forms.ModelForm):
'''
Form class for arc node form.
Handles properties, but not position or parent arc.
'''
def __init__(self, *args, **kwargs):
arc = kwargs.pop('arc')
super().__init__(*args, **kwargs)
if arc:
outline = arc.outline
self.fields['assoc_characters'].queryset = CharacterInstance.objects.filter(outline=outline)
self.fields['assoc_locations'].queryset = LocationInstance.objects.filter(outline=outline)
self.fields['story_element_node'].queryset = StoryElementNode.objects.filter(outline=outline, depth__gt=1)
class Meta:
model = ArcElementNode
fields = (
'arc_element_type',
'description',
'assoc_characters',
'assoc_locations',
'story_element_node',
)
class StoryNodeForm(forms.ModelForm):
'''
Form class for story node form.
Handles properties but not position or parent arc.
'''
def __init__(self, *args, **kwargs):
outline = kwargs.pop('outline')
super().__init__(*args, **kwargs)
if outline:
self.fields['assoc_characters'].queryset = CharacterInstance.objects.filter(outline=outline)
self.fields['assoc_locations'].queryset = LocationInstance.objects.filter(outline=outline)
class Meta:
model = StoryElementNode
fields = {
'name',
'story_element_type',
'description',
'assoc_characters',
'assoc_locations',
}