mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Add ChoiceList type, cell widget, and editor widget.
Summary: - Adds a new ChoiceList type, and widgets to view and edit it. - Store in SQLite as a JSON string - Support conversions between ChoiceList and other types Test Plan: Added browser tests, and a test for how these values are stored Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D2803
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import json
|
||||
import types
|
||||
from collections import namedtuple
|
||||
|
||||
@@ -294,6 +295,26 @@ class PositionColumn(NumericColumn):
|
||||
return new_values, [(self._sorted_rows[i], pos) for (i, pos) in adjustments]
|
||||
|
||||
|
||||
class ChoiceListColumn(BaseColumn):
|
||||
"""
|
||||
ChoiceListColumn's default value is None, but is presented to formulas as the empty list.
|
||||
"""
|
||||
def set(self, row_id, value):
|
||||
# When a JSON string is loaded, set it to a tuple parsed from it. When a list is loaded,
|
||||
# convert to a tuple to keep values immutable.
|
||||
if isinstance(value, basestring) and value.startswith('['):
|
||||
try:
|
||||
value = tuple(json.loads(value))
|
||||
except Exception:
|
||||
pass
|
||||
elif isinstance(value, list):
|
||||
value = tuple(value)
|
||||
super(ChoiceListColumn, self).set(row_id, value)
|
||||
|
||||
def _make_rich_value(self, typed_value):
|
||||
return () if typed_value is None else typed_value
|
||||
|
||||
|
||||
class BaseReferenceColumn(BaseColumn):
|
||||
"""
|
||||
Base class for ReferenceColumn and ReferenceListColumn.
|
||||
@@ -386,6 +407,7 @@ class ReferenceListColumn(BaseReferenceColumn):
|
||||
usertypes.BaseColumnType.ColType = DataColumn
|
||||
usertypes.Reference.ColType = ReferenceColumn
|
||||
usertypes.ReferenceList.ColType = ReferenceListColumn
|
||||
usertypes.ChoiceList.ColType = ChoiceListColumn
|
||||
usertypes.DateTime.ColType = DateTimeColumn
|
||||
usertypes.Date.ColType = DateColumn
|
||||
usertypes.PositionNumber.ColType = PositionColumn
|
||||
|
||||
@@ -6,7 +6,7 @@ a consistent API accessible with only "import grist".
|
||||
|
||||
# These imports are used in processing generated usercode.
|
||||
from usertypes import Any, Text, Blob, Int, Bool, Date, DateTime, \
|
||||
Numeric, Choice, Id, Attachments, AltText, ifError
|
||||
Numeric, Choice, ChoiceList, Id, Attachments, AltText, ifError
|
||||
from usertypes import PositionNumber, ManualSortPos, Reference, ReferenceList, formulaType
|
||||
from table import UserTable
|
||||
from records import Record, RecordSet
|
||||
|
||||
@@ -11,7 +11,10 @@ Python's array.array. However, at least on the Python side, it means that we nee
|
||||
data structure for values of the wrong type, and the memory savings aren't that great to be worth
|
||||
the extra complexity.
|
||||
"""
|
||||
import csv
|
||||
import cStringIO
|
||||
import datetime
|
||||
import json
|
||||
import six
|
||||
import objtypes
|
||||
from objtypes import AltText
|
||||
@@ -29,6 +32,7 @@ _type_defaults = {
|
||||
'Blob': None,
|
||||
'Bool': False,
|
||||
'Choice': '',
|
||||
'ChoiceList': None,
|
||||
'Date': None,
|
||||
'DateTime': None,
|
||||
'Id': 0,
|
||||
@@ -319,6 +323,53 @@ class Choice(Text):
|
||||
pass
|
||||
|
||||
|
||||
class ChoiceList(BaseColumnType):
|
||||
"""
|
||||
ChoiceList is the type for a field holding a list of strings from a set of acceptable choices.
|
||||
"""
|
||||
def do_convert(self, value):
|
||||
if not value:
|
||||
return None
|
||||
elif isinstance(value, basestring):
|
||||
# If it's a string that looks like JSON, try to parse it as such.
|
||||
if value.startswith('['):
|
||||
try:
|
||||
return tuple(str(item) for item in json.loads(value))
|
||||
except Exception:
|
||||
pass
|
||||
return value
|
||||
else:
|
||||
# Accepts other kinds of iterables; if that doesn't work, fail the conversion too.
|
||||
return tuple(str(item) for item in value)
|
||||
|
||||
@classmethod
|
||||
def is_right_type(cls, value):
|
||||
return value is None or (isinstance(value, (tuple, list)) and
|
||||
all(isinstance(item, basestring) for item in value))
|
||||
|
||||
@classmethod
|
||||
def typeConvert(cls, value):
|
||||
if isinstance(value, basestring) and not value.startswith('['):
|
||||
# Try to parse as CSV. If this doesn't work, we'll still try usual conversions later.
|
||||
try:
|
||||
tags = next(csv.reader([value]))
|
||||
return tuple(t.strip() for t in tags if t.strip())
|
||||
except Exception:
|
||||
pass
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def toString(cls, value):
|
||||
if isinstance(value, (tuple, list)):
|
||||
try:
|
||||
buf = cStringIO.StringIO()
|
||||
csv.writer(buf).writerow(value)
|
||||
return buf.getvalue().strip()
|
||||
except Exception:
|
||||
pass
|
||||
return value
|
||||
|
||||
|
||||
class PositionNumber(BaseColumnType):
|
||||
"""
|
||||
PositionNumber is the type for a position field used to order records in record lists.
|
||||
|
||||
Reference in New Issue
Block a user