(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:
Dmitry S
2021-05-12 10:34:49 -04:00
parent e55fba24e7
commit 8d62a857e1
13 changed files with 615 additions and 69 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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.