@sw-tsdk/plugin-connector 3.13.1 → 3.13.2-next.3dfd44a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +18 -18
  2. package/lib/commands/connector/build.js +168 -44
  3. package/lib/commands/connector/build.js.map +1 -1
  4. package/lib/commands/connector/sign.js +108 -12
  5. package/lib/commands/connector/sign.js.map +1 -1
  6. package/lib/commands/connector/validate.js +110 -10
  7. package/lib/commands/connector/validate.js.map +1 -1
  8. package/lib/commands/migrator/convert.d.ts +3 -0
  9. package/lib/commands/migrator/convert.js +201 -20
  10. package/lib/commands/migrator/convert.js.map +1 -1
  11. package/lib/templates/migrator-runners/plugin_override.txt +76 -4
  12. package/lib/templates/migrator-runners/runner_override.txt +30 -0
  13. package/lib/templates/migrator-runners/script_override.txt +77 -5
  14. package/lib/templates/swimlane/__init__.py +18 -0
  15. package/lib/templates/swimlane/core/__init__.py +0 -0
  16. package/lib/templates/swimlane/core/adapters/__init__.py +10 -0
  17. package/lib/templates/swimlane/core/adapters/app.py +59 -0
  18. package/lib/templates/swimlane/core/adapters/app_revision.py +49 -0
  19. package/lib/templates/swimlane/core/adapters/helper.py +84 -0
  20. package/lib/templates/swimlane/core/adapters/record.py +468 -0
  21. package/lib/templates/swimlane/core/adapters/record_revision.py +43 -0
  22. package/lib/templates/swimlane/core/adapters/report.py +65 -0
  23. package/lib/templates/swimlane/core/adapters/task.py +58 -0
  24. package/lib/templates/swimlane/core/adapters/usergroup.py +183 -0
  25. package/lib/templates/swimlane/core/bulk.py +48 -0
  26. package/lib/templates/swimlane/core/cache.py +165 -0
  27. package/lib/templates/swimlane/core/client.py +466 -0
  28. package/lib/templates/swimlane/core/cursor.py +100 -0
  29. package/lib/templates/swimlane/core/fields/__init__.py +46 -0
  30. package/lib/templates/swimlane/core/fields/attachment.py +82 -0
  31. package/lib/templates/swimlane/core/fields/base/__init__.py +15 -0
  32. package/lib/templates/swimlane/core/fields/base/cursor.py +90 -0
  33. package/lib/templates/swimlane/core/fields/base/field.py +149 -0
  34. package/lib/templates/swimlane/core/fields/base/multiselect.py +116 -0
  35. package/lib/templates/swimlane/core/fields/comment.py +48 -0
  36. package/lib/templates/swimlane/core/fields/datetime.py +112 -0
  37. package/lib/templates/swimlane/core/fields/history.py +28 -0
  38. package/lib/templates/swimlane/core/fields/list.py +266 -0
  39. package/lib/templates/swimlane/core/fields/number.py +38 -0
  40. package/lib/templates/swimlane/core/fields/reference.py +169 -0
  41. package/lib/templates/swimlane/core/fields/text.py +30 -0
  42. package/lib/templates/swimlane/core/fields/tracking.py +10 -0
  43. package/lib/templates/swimlane/core/fields/usergroup.py +137 -0
  44. package/lib/templates/swimlane/core/fields/valueslist.py +70 -0
  45. package/lib/templates/swimlane/core/resolver.py +46 -0
  46. package/lib/templates/swimlane/core/resources/__init__.py +0 -0
  47. package/lib/templates/swimlane/core/resources/app.py +136 -0
  48. package/lib/templates/swimlane/core/resources/app_revision.py +43 -0
  49. package/lib/templates/swimlane/core/resources/attachment.py +64 -0
  50. package/lib/templates/swimlane/core/resources/base.py +55 -0
  51. package/lib/templates/swimlane/core/resources/comment.py +33 -0
  52. package/lib/templates/swimlane/core/resources/record.py +499 -0
  53. package/lib/templates/swimlane/core/resources/record_revision.py +44 -0
  54. package/lib/templates/swimlane/core/resources/report.py +259 -0
  55. package/lib/templates/swimlane/core/resources/revision_base.py +69 -0
  56. package/lib/templates/swimlane/core/resources/task.py +16 -0
  57. package/lib/templates/swimlane/core/resources/usergroup.py +166 -0
  58. package/lib/templates/swimlane/core/search.py +31 -0
  59. package/lib/templates/swimlane/core/wrappedsession.py +12 -0
  60. package/lib/templates/swimlane/exceptions.py +191 -0
  61. package/lib/templates/swimlane/utils/__init__.py +132 -0
  62. package/lib/templates/swimlane/utils/date_validator.py +4 -0
  63. package/lib/templates/swimlane/utils/list_validator.py +7 -0
  64. package/lib/templates/swimlane/utils/str_validator.py +10 -0
  65. package/lib/templates/swimlane/utils/version.py +101 -0
  66. package/lib/transformers/base-transformer.js +61 -14
  67. package/lib/transformers/base-transformer.js.map +1 -1
  68. package/lib/transformers/connector-generator.d.ts +104 -2
  69. package/lib/transformers/connector-generator.js +1234 -51
  70. package/lib/transformers/connector-generator.js.map +1 -1
  71. package/lib/types/migrator-types.d.ts +22 -0
  72. package/lib/types/migrator-types.js.map +1 -1
  73. package/oclif.manifest.json +1 -1
  74. package/package.json +6 -6
@@ -0,0 +1,15 @@
1
+ """Base classes used to build field abstractions"""
2
+ from .cursor import CursorField, FieldCursor
3
+ from .field import Field
4
+ from .multiselect import MultiSelectField, MultiSelectCursor
5
+
6
+
7
+ class ReadOnly(Field):
8
+ """Mixin explicitly disabling setting value via python"""
9
+
10
+ def __init__(self, *args, **kwargs):
11
+ super(ReadOnly, self).__init__(*args, **kwargs)
12
+
13
+ self.readonly = True
14
+
15
+
@@ -0,0 +1,90 @@
1
+ import weakref
2
+
3
+ from swimlane.core.cursor import Cursor
4
+ from swimlane.core.resolver import SwimlaneResolver
5
+ from .field import Field
6
+
7
+
8
+ class FieldCursor(Cursor, SwimlaneResolver):
9
+ """Base class for encapsulating a field instance's complex logic
10
+
11
+ Useful in abstracting away extra request(s), lazy evaluation, pagination, intensive calculations, etc.
12
+ """
13
+
14
+ def __init__(self, field, initial_elements=None):
15
+ SwimlaneResolver.__init__(self, field.record._swimlane)
16
+ Cursor.__init__(self)
17
+
18
+ self._elements = initial_elements or self._elements
19
+
20
+ self.__field_name = field.name
21
+ self.__record_ref = weakref.ref(field.record)
22
+ self.__field_ref = weakref.ref(field)
23
+
24
+ def __repr__(self):
25
+ # pylint: disable=missing-format-attribute
26
+ return '<{self.__class__.__name__}: {self._record!r}["{self._field.name}"] ({length})>'.format(
27
+ self=self,
28
+ length=len(self)
29
+ )
30
+
31
+ def __eq__(self, other):
32
+ return isinstance(other, self.__class__) and other._record.id == self._record.id
33
+
34
+ def _sync_field(self):
35
+ """Set source field value to current cursor value"""
36
+ self._field.set_python(self._evaluate())
37
+
38
+ @property
39
+ def _record(self):
40
+ return self.__record_ref()
41
+
42
+ @property
43
+ def _field(self):
44
+ field = self.__field_ref()
45
+ # Occurs when a record is saved and reinitialized, creating new Field instances and losing the existing weakref
46
+ # Update weakref to point to new Field instance of the same name
47
+ if field is None:
48
+ field = self._record.get_field(self.__field_name)
49
+ self.__field_ref = weakref.ref(field)
50
+ return field
51
+
52
+
53
+ class CursorField(Field):
54
+ """Returns a proxy-like FieldCursor instance to support additional functionality"""
55
+
56
+ cursor_class = None
57
+
58
+ def __init__(self, *args, **kwargs):
59
+ super(CursorField, self).__init__(*args, **kwargs)
60
+
61
+ self._cursor = None
62
+
63
+ def get_initial_elements(self):
64
+ """Return initial elements to be passed with cursor instantiation"""
65
+ return self._get()
66
+
67
+ def _set(self, value):
68
+ self._cursor = None
69
+ super(CursorField, self)._set(value)
70
+
71
+ @property
72
+ def cursor(self):
73
+ """Cache and return cursor_class instance"""
74
+ if self._cursor is None:
75
+ # pylint: disable=not-callable
76
+ self._cursor = self.cursor_class(self, self.get_initial_elements())
77
+
78
+ return self._cursor
79
+
80
+ def get_python(self):
81
+ """Create, cache, and return the appropriate cursor instance"""
82
+ return self.cursor
83
+
84
+ def for_json(self):
85
+ """Return list of all cursor items, calling .for_json() if available for best representations"""
86
+ cursor = super(CursorField, self).for_json()
87
+ if cursor is not None:
88
+ # Lambda called immediately, false-positive from pylint on closure scope warning
89
+ # pylint: disable=cell-var-from-loop
90
+ return [getattr(item, 'for_json', lambda: item)() for item in cursor]
@@ -0,0 +1,149 @@
1
+ import weakref
2
+
3
+ from swimlane.core.resolver import SwimlaneResolver
4
+ from swimlane.exceptions import ValidationError
5
+
6
+
7
+ class Field(SwimlaneResolver):
8
+ """Base class for abstracting Swimlane complex types"""
9
+
10
+ field_type = None
11
+
12
+ # Sentinel representing a field that has no current value
13
+ _unset = object()
14
+
15
+ # List of supported types, leave blank to disable type validation
16
+ supported_types = []
17
+
18
+ # Checks if bulk modify is supported for field
19
+ bulk_modify_support = True
20
+
21
+ def __init__(self, name, record):
22
+ """Value not included during instantiation to prevent ambiguity between python and swimlane representations"""
23
+ super(Field, self).__init__(record._swimlane)
24
+
25
+ self.name = name
26
+ self.__record_ref = weakref.ref(record)
27
+ self._value = self._unset
28
+
29
+ self.field_definition = self.record.app.get_field_definition_by_name(self.name)
30
+ self.key = self.field_definition.get('key')
31
+ self.id = self.field_definition['id']
32
+ self.input_type = self.field_definition.get('inputType')
33
+ self.required = self.field_definition.get('required', False)
34
+ self.readonly = bool(self.field_definition.get('formula', self.field_definition.get('readOnly', False)))
35
+ self.multiselect = self.field_definition.get('selectionType', 'single') == 'multi'
36
+
37
+ def __repr__(self):
38
+ return '<{class_name}: {py!r}>'.format(class_name=self.__class__.__name__, py=self.get_python())
39
+
40
+ @property
41
+ def record(self):
42
+ """Resolve weak reference to parent record"""
43
+ return self.__record_ref()
44
+
45
+ def _get(self):
46
+ """Default getter used for both representations unless overridden"""
47
+ return self._value
48
+
49
+ def get_item(self):
50
+ """Return best python representation of field value for get attribute method"""
51
+ return self.get_python()
52
+
53
+ def get_python(self):
54
+ """Return best python representation of field value"""
55
+ return self._get()
56
+
57
+ def get_batch_representation(self):
58
+ """Return best batch process representation of field value"""
59
+ return self._get()
60
+
61
+ def get_swimlane(self):
62
+ """Return best swimlane representation of field value"""
63
+ return self.cast_to_swimlane(self._get())
64
+
65
+ def get_report(self, value):
66
+ """Return provided field Python value formatted for use in report filter"""
67
+ if self.multiselect:
68
+ value = value or []
69
+ try:
70
+ list_vars = vars(value)
71
+ if "multiselect" in list_vars:
72
+ pass
73
+ except :
74
+ if not isinstance(value, list) :
75
+ raise TypeError("Value Expected a list, but got something else.")
76
+ children = []
77
+
78
+ for child in value:
79
+ id = self.cast_to_report(child)
80
+ if id:
81
+ children.append(id)
82
+
83
+ return children
84
+
85
+ return self.cast_to_report(value)
86
+
87
+ def get_bulk_modify(self, value):
88
+ """Return value in format for bulk modify"""
89
+ if self.multiselect:
90
+ value = value or []
91
+ return [self.cast_to_bulk_modify(child) for child in value]
92
+
93
+ return self.cast_to_bulk_modify(value)
94
+
95
+ def cast_to_python(self, value):
96
+ """Called during set_swimlane, should accept a single raw value as provided from API
97
+
98
+ Defaults to no-op
99
+ """
100
+ return value
101
+
102
+ def cast_to_swimlane(self, value):
103
+ """Called during get_swimlane, should accept a python value and return swimlane representation
104
+
105
+ Defaults to no-op
106
+ """
107
+ return value
108
+
109
+ def cast_to_report(self, value):
110
+ """Cast single value to report format, defaults to cast_to_swimlane(value)"""
111
+ return self.cast_to_swimlane(value)
112
+
113
+ def cast_to_bulk_modify(self, value):
114
+ """Cast single value to bulk modify format, defaults to cast_to_report with added validation"""
115
+ self.validate_value(value)
116
+ return self.cast_to_report(value)
117
+
118
+ def validate_value(self, value):
119
+ """Validate value is an acceptable type during set_python operation"""
120
+ if self.readonly and not self._swimlane._write_to_read_only:
121
+ raise ValidationError(self.record, 'Cannot set readonly field "{}"'.format(self.name))
122
+ if value not in (None, self._unset):
123
+ if self.supported_types and not isinstance(value, tuple(self.supported_types)):
124
+ raise ValidationError(self.record, 'Field "{}" expects one of {}, got "{}" instead'.format(
125
+ self.name,
126
+ ', '.join([repr(t.__name__) for t in self.supported_types]),
127
+ type(value).__name__)
128
+ )
129
+
130
+ def _set(self, value):
131
+ """Default setter used for both representations unless overridden"""
132
+ self._value = value
133
+ self.record._raw['values'][self.id] = self.get_swimlane()
134
+
135
+ def set_python(self, value):
136
+ """Set field internal value from the python representation of field value"""
137
+ self.validate_value(value)
138
+ return self._set(value)
139
+
140
+ def set_swimlane(self, value):
141
+ """Set field internal value from the swimlane representation of field value"""
142
+ return self._set(self.cast_to_python(value))
143
+
144
+ def for_json(self):
145
+ """Return json.dump()-compatible representation of field value
146
+
147
+ .. versionadded:: 4.1.0
148
+ """
149
+ return self.get_python()
@@ -0,0 +1,116 @@
1
+ from sortedcontainers import SortedSet
2
+
3
+ from swimlane.core.resources.usergroup import User
4
+
5
+ from .cursor import CursorField, FieldCursor
6
+
7
+
8
+ class MultiSelectCursor(FieldCursor):
9
+ """Cursor allowing setting and unsetting values on a MultiSelectField
10
+
11
+ Respects parent field's validation
12
+ """
13
+
14
+ def __init__(self, *args, **kwargs):
15
+ super(MultiSelectCursor, self).__init__(*args, **kwargs)
16
+
17
+ self._elements = filter(lambda e: e is not None, self._elements)
18
+ self._elements = SortedSet(self._elements)
19
+
20
+ def select(self, element):
21
+ """Add an element to the set of selected elements
22
+
23
+ Proxy to internal set.add and sync field
24
+ """
25
+ self._field.validate_value(element)
26
+ self._elements.add(element)
27
+ self._sync_field()
28
+
29
+ def deselect(self, element):
30
+ """Remove an element from the set of selected elements
31
+
32
+ Proxy to internal set.remove and sync field
33
+ """
34
+ self._elements.remove(element)
35
+ self._sync_field()
36
+
37
+
38
+ class MultiSelectField(CursorField):
39
+ """Base class for fields that can be multi-selection or single-selection field"""
40
+
41
+ cursor_class = MultiSelectCursor
42
+
43
+ def get_python(self):
44
+ """Only return cursor instance if configured for multiselect"""
45
+ if self.multiselect:
46
+ return super(MultiSelectField, self).get_python()
47
+
48
+ return self._get()
49
+
50
+ def get_swimlane(self):
51
+ """Handle multi-select and single-select modes"""
52
+ if self.multiselect:
53
+ value = self._get()
54
+ children = []
55
+ if value:
56
+ for child in value:
57
+ children.append(self.cast_to_swimlane(child))
58
+ return children
59
+ return None
60
+ return super(MultiSelectField, self).get_swimlane()
61
+
62
+ def _set(self, value):
63
+ """Override to treat empty lists as None"""
64
+ return super(MultiSelectField, self)._set(value or None)
65
+
66
+ def set_python(self, value):
67
+ """Override to remove key from raw data when empty to work with server 2.16+ validation"""
68
+ if self.multiselect:
69
+ value = value or []
70
+ elements = []
71
+
72
+ if not isinstance(
73
+ value,
74
+ (
75
+ list,
76
+ MultiSelectCursor,
77
+ SortedSet,
78
+ User,
79
+ ),
80
+ ):
81
+ value = [value]
82
+
83
+ for element in value:
84
+ self.validate_value(element)
85
+ elements.append(element)
86
+
87
+ value = elements
88
+ else:
89
+ self.validate_value(value)
90
+
91
+ self._set(value)
92
+
93
+ def set_swimlane(self, value):
94
+ """Cast all multi-select elements to correct internal type like single-select mode"""
95
+ if self.multiselect:
96
+ value = value or []
97
+ children = []
98
+
99
+ for child in value:
100
+ children.append(self.cast_to_python(child))
101
+
102
+ return self._set(children)
103
+
104
+ return super(MultiSelectField, self).set_swimlane(value)
105
+
106
+ def for_json(self):
107
+ """Handle multi-select vs single-select"""
108
+
109
+ if self.multiselect:
110
+ return super(MultiSelectField, self).for_json()
111
+
112
+ value = self.get_python()
113
+ if hasattr(value, 'for_json'):
114
+ return value.for_json()
115
+
116
+ return value
@@ -0,0 +1,48 @@
1
+ import pendulum
2
+
3
+ from swimlane.core.resources.comment import Comment
4
+ from .base import CursorField, FieldCursor, ReadOnly
5
+
6
+
7
+ class CommentCursor(FieldCursor):
8
+ """Returned by CommentField to allow iteration and creation of Comment instances"""
9
+
10
+ def comment(self, message, rich_text=False):
11
+ """Add new comment to record comment field"""
12
+ message = str(message)
13
+ if not isinstance(rich_text, bool):
14
+ raise ValueError('rich_text must be a boolean value.')
15
+
16
+ sw_repr = {
17
+ '$type': 'Core.Models.Record.Comments, Core',
18
+ 'createdByUser': self._record._swimlane.user.as_usergroup_selection(),
19
+ 'createdDate': pendulum.now().to_rfc3339_string(),
20
+ 'message': message,
21
+ 'isRichText': rich_text
22
+ }
23
+
24
+ comment = Comment(self._swimlane, sw_repr)
25
+ self._elements.append(comment)
26
+
27
+ self._record._raw['comments'].setdefault(self._field.id, [])
28
+ self._record._raw['comments'][self._field.id].append(comment._raw)
29
+
30
+ # Tracking comment changes for patch endpoint
31
+ self._record._comments_modified = True
32
+
33
+ return comment
34
+
35
+
36
+ class CommentsField(ReadOnly, CursorField):
37
+
38
+ field_type = (
39
+ 'Core.Models.Fields.CommentsField, Core',
40
+ 'Core.Models.Fields.Comments.CommentsField, Core'
41
+ )
42
+ cursor_class = CommentCursor
43
+ bulk_modify_support = False
44
+
45
+ def get_initial_elements(self):
46
+ raw_comments = self.record._raw['comments'].get(self.id, [])
47
+
48
+ return [Comment(self.record._swimlane, raw) for raw in raw_comments]
@@ -0,0 +1,112 @@
1
+ from __future__ import absolute_import
2
+ from swimlane.utils.date_validator import is_datetime
3
+
4
+ from datetime import date, datetime, time, timedelta
5
+
6
+ import math
7
+ import pendulum
8
+
9
+ from .base import Field
10
+
11
+ UTC = pendulum.timezone('UTC')
12
+
13
+
14
+ class DatetimeField(Field):
15
+
16
+ field_type = 'Core.Models.Fields.Date.DateField, Core'
17
+
18
+ datetime_format = '%Y-%m-%dT%H:%M:%S.%fZ'
19
+
20
+ _type_date = 'date'
21
+ _type_time = 'time'
22
+ _type_interval = 'timespan'
23
+
24
+ # All others default to datetime
25
+ _input_type_map = {
26
+ _type_interval: [timedelta],
27
+ _type_date: [datetime, date],
28
+ _type_time: [datetime, time]
29
+ }
30
+
31
+ def __init__(self, *args, **kwargs):
32
+ super(DatetimeField, self).__init__(*args, **kwargs)
33
+
34
+ # Determine supported_types after inspecting input subtype
35
+ self.supported_types = self._input_type_map.get(self.input_type, [datetime])
36
+
37
+ def _set(self, value):
38
+ # Force to appropriate Pendulum instance for consistency
39
+ if value is not None:
40
+ if self.input_type != self._type_interval:
41
+ if self.input_type == self._type_date:
42
+ # Pendulum date
43
+ if isinstance(value, date):
44
+ value = pendulum.DateTime.combine(value, pendulum.time(0))
45
+ elif self.input_type == self._type_time:
46
+ # Pendulum time
47
+ if isinstance(value, time):
48
+ value = pendulum.DateTime.combine(pendulum.today().date(), value)
49
+
50
+ # Convert to Pendulum instance in UTC
51
+ value = UTC.convert(pendulum.instance(value))
52
+ # Drop nanosecond precision to match Mongo precision
53
+ value = value.set(microsecond=int(math.floor(value.microsecond / 1000) * 1000))
54
+
55
+ return super(DatetimeField, self)._set(value)
56
+
57
+ def cast_to_python(self, value):
58
+ if value is not None:
59
+ if self.input_type == self._type_interval:
60
+ value = pendulum.duration(milliseconds=int(value))
61
+ else:
62
+ value = pendulum.parse(value)
63
+
64
+ return value
65
+
66
+ def get_python(self):
67
+ """Coerce to best date type representation for the field subtype"""
68
+ value = super(DatetimeField, self).get_python()
69
+
70
+ if value is not None:
71
+ # Handle subtypes with matching Pendulum types
72
+ if self.input_type == self._type_time:
73
+ value = value.time()
74
+ if self.input_type == self._type_date:
75
+ value = value.date()
76
+
77
+ return value
78
+
79
+ @classmethod
80
+ def format_datetime(cls, target_datetime):
81
+ """Format datetime as expected by Swimlane API"""
82
+ if not is_datetime(target_datetime):
83
+ target_datetime = datetime.combine(target_datetime, datetime.min.time())
84
+ return UTC.convert(target_datetime).strftime(cls.datetime_format)
85
+
86
+ def cast_to_swimlane(self, value):
87
+ """Return datetimes formatted as expected by API and timespans as millisecond epochs"""
88
+ if value is None:
89
+ return value
90
+
91
+ if self.input_type == self._type_interval:
92
+ return value.in_seconds() * 1000
93
+
94
+ return self.format_datetime(value)
95
+
96
+ def get_batch_representation(self):
97
+ """Return best batch process representation of field value"""
98
+ return self.get_swimlane()
99
+
100
+ def for_json(self):
101
+ """Return date ISO8601 string formats for datetime, date, and time values, milliseconds for intervals"""
102
+ value = super(DatetimeField, self).for_json()
103
+
104
+ # Order of instance checks matters for proper inheritance checks
105
+ if isinstance(value, pendulum.Duration):
106
+ return value.in_seconds() * 1000
107
+ if isinstance(value, datetime):
108
+ return self.format_datetime(value)
109
+ if isinstance(value, pendulum.Time):
110
+ return str(value)
111
+ if isinstance(value, pendulum.Date):
112
+ return value.to_date_string()
@@ -0,0 +1,28 @@
1
+ from .base import CursorField, FieldCursor, ReadOnly
2
+
3
+
4
+ class RevisionCursor(FieldCursor):
5
+ """An iterable object that automatically lazy retrieves and caches history data for a record from API"""
6
+
7
+ def __init__(self, *args, **kwargs):
8
+ super(RevisionCursor, self).__init__(*args, **kwargs)
9
+ self.__retrieved = False
10
+
11
+ def _evaluate(self):
12
+ """Lazily retrieves, caches, and returns the list of record _revisions"""
13
+ if not self.__retrieved:
14
+ self._elements = self._retrieve_revisions()
15
+ self.__retrieved = True
16
+
17
+ return super(RevisionCursor, self)._evaluate()
18
+
19
+ def _retrieve_revisions(self):
20
+ """Populate RecordRevision instances."""
21
+ return self._record.revisions.get_all()
22
+
23
+
24
+ class HistoryField(ReadOnly, CursorField):
25
+
26
+ field_type = 'Core.Models.Fields.History.HistoryField, Core'
27
+ cursor_class = RevisionCursor
28
+ bulk_modify_support = False