Source code for swimlane.core.fields.reference
import logging
import six
from sortedcontainers import SortedDict
from swimlane.core.fields.base import CursorField, FieldCursor
from swimlane.core.resources.record import Record
from swimlane.exceptions import ValidationError, SwimlaneHTTP400Error
logger = logging.getLogger(__name__)
[docs]class ReferenceCursor(FieldCursor):
"""Handles lazy retrieval of target records"""
def __init__(self, *args, **kwargs):
super(ReferenceCursor, self).__init__(*args, **kwargs)
self._elements = self._elements or SortedDict()
@property
def target_app(self):
"""Make field's target_app available on cursor"""
return self._field.target_app
def __getitem__(self, item):
record_id, record = [kv for kv in self._elements.items()][item]
if record is self._field._unset:
try:
records_get = self.target_app.records.get(id=record_id)
self._elements[record_id] = records_get
return records_get
except SwimlaneHTTP400Error:
logger.debug('Received 400 response retrieving record "{}", ignoring assumed orphaned record')
else:
return record
def __iter__(self):
for record_id, record in six.iteritems(self._elements):
if record is self._field._unset:
try:
records_get = self.target_app.records.get(id=record_id)
self._elements[record_id] = records_get
yield records_get
except SwimlaneHTTP400Error:
logger.debug('Received 400 response retrieving record "{}", ignoring assumed orphaned record')
else:
yield record
[docs] def add(self, record):
"""Add a reference to the provided record"""
self._field.validate_value(record)
self._elements[record.id] = record
self._sync_field()
[docs] def remove(self, record):
"""Remove a reference to the provided record"""
self._field.validate_value(record)
del self._elements[record.id]
self._sync_field()
[docs]class ReferenceField(CursorField):
field_type = 'Core.Models.Fields.Reference.ReferenceField, Core'
supported_types = (Record,)
cursor_class = ReferenceCursor
def __init__(self, *args, **kwargs):
super(ReferenceField, self).__init__(*args, **kwargs)
self.__target_app_id = self.field_definition['targetId']
self.__target_app = None
@property
def target_app(self):
"""Defer target app retrieval until requested"""
if self.__target_app is None:
self.__target_app = self._swimlane.apps.get(id=self.__target_app_id)
return self.__target_app
[docs] def validate_value(self, value):
"""Validate provided record is a part of the appropriate target app for the field"""
if value not in (None, self._unset):
if value.app != self.target_app:
raise ValidationError(
self.record,
'Reference field "{}" has target app "{}", cannot reference record "{}" from app "{}"'.format(
self.name,
self.target_app,
value,
value.app
)
)
def _set(self, value):
self._cursor = None
self._value = value or None
[docs] def set_swimlane(self, value):
"""Store record ids in separate location for later use, but ignore initial value"""
# Values come in as a list of record ids or None
value = value or []
if isinstance(value, dict):
value = value["_v"] if "_v" in value else []
records = SortedDict()
for record_id in value:
records[record_id] = self._unset
return super(ReferenceField, self).set_swimlane(records)
[docs] def set_python(self, value):
"""With changes to Lazy instrumentation _evaluation returns SortedDictionary, unfortunately this
is still public method that can be accessed in the old way, therefore it was split in two.
"""
if isinstance(value, SortedDict):
self.set_python_raw(value)
else:
self.set_python_old(value)
[docs] def set_python_old(self, value):
"""Expect list of record instances, convert to a SortedDict for internal representation"""
if not self.multiselect:
if value and not isinstance(value, list):
value = [value]
value = value or []
records = SortedDict()
for record in value:
self.validate_value(record)
records[record.id] = record
self._set(records)
self.record._raw['values'][self.id] = self.get_swimlane()
[docs] def get_swimlane(self):
"""Return list of record ids"""
value = super(ReferenceField, self).get_swimlane()
if value:
ids = list(value.keys())
return ids
[docs] def get_item(self):
"""Return cursor if multi-select, direct value if single-select"""
cursor = super(ReferenceField, self).get_python()
if self.multiselect:
return cursor
else:
try:
return cursor[0]
except IndexError:
return None
[docs] def get_batch_representation(self):
"""Return best batch process representation of field value"""
return self.get_swimlane()
[docs] def cast_to_report(self, value):
return value.id
[docs] def for_json(self):
return self.get_swimlane()
[docs] def set_python_raw(self, value):
self._set(value)
self.record._raw['values'][self.id] = self.get_swimlane()