mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
f7a9638992
Summary: Added CONTAINS 'function' which can be used in lookups Changed LookupMapColumn._row_key_map to use right=set so one row can have many keys when CONTAINS is used. Use CONTAINS to implement group column in summary table, while helper column in source table can reference and create multiple rows in summary table, especially when summarising by ChoiceList columns. Use itertools.product to generate all combinations of lookup keys and groupby values. cleanup Test Plan: Added python unit tests. Reviewers: dsagal Reviewed By: dsagal Subscribers: paulfitz, dsagal Differential Revision: https://phab.getgrist.com/D2900
175 lines
6.5 KiB
Python
175 lines
6.5 KiB
Python
# pylint: disable=redefined-builtin, line-too-long
|
|
from collections import OrderedDict, namedtuple
|
|
import os
|
|
|
|
import six
|
|
from six.moves import urllib_parse
|
|
from .unimplemented import unimplemented
|
|
|
|
@unimplemented
|
|
def ADDRESS(row, column, absolute_relative_mode, use_a1_notation, sheet):
|
|
"""Returns a cell reference as a string."""
|
|
raise NotImplementedError()
|
|
|
|
@unimplemented
|
|
def CHOOSE(index, choice1, choice2):
|
|
"""Returns an element from a list of choices based on index."""
|
|
raise NotImplementedError()
|
|
|
|
@unimplemented
|
|
def COLUMN(cell_reference=None):
|
|
"""Returns the column number of a specified cell, with `A=1`."""
|
|
raise NotImplementedError()
|
|
|
|
@unimplemented
|
|
def COLUMNS(range):
|
|
"""Returns the number of columns in a specified array or range."""
|
|
raise NotImplementedError()
|
|
|
|
@unimplemented
|
|
def GETPIVOTDATA(value_name, any_pivot_table_cell, original_column_1, pivot_item_1=None, *args):
|
|
"""Extracts an aggregated value from a pivot table that corresponds to the specified row and column headings."""
|
|
raise NotImplementedError()
|
|
|
|
@unimplemented
|
|
def HLOOKUP(search_key, range, index, is_sorted):
|
|
"""Horizontal lookup. Searches across the first row of a range for a key and returns the value of a specified cell in the column found."""
|
|
raise NotImplementedError()
|
|
|
|
@unimplemented
|
|
def HYPERLINK(url, link_label):
|
|
"""Creates a hyperlink inside a cell."""
|
|
raise NotImplementedError()
|
|
|
|
@unimplemented
|
|
def INDEX(reference, row, column):
|
|
"""Returns the content of a cell, specified by row and column offset."""
|
|
raise NotImplementedError()
|
|
|
|
@unimplemented
|
|
def INDIRECT(cell_reference_as_string):
|
|
"""Returns a cell reference specified by a string."""
|
|
raise NotImplementedError()
|
|
|
|
@unimplemented
|
|
def LOOKUP(search_key, search_range_or_search_result_array, result_range=None):
|
|
"""Looks through a row or column for a key and returns the value of the cell in a result range located in the same position as the search row or column."""
|
|
raise NotImplementedError()
|
|
|
|
@unimplemented
|
|
def MATCH(search_key, range, search_type):
|
|
"""Returns the relative position of an item in a range that matches a specified value."""
|
|
raise NotImplementedError()
|
|
|
|
@unimplemented
|
|
def OFFSET(cell_reference, offset_rows, offset_columns, height, width):
|
|
"""Returns a range reference shifted a specified number of rows and columns from a starting cell reference."""
|
|
raise NotImplementedError()
|
|
|
|
@unimplemented
|
|
def ROW(cell_reference):
|
|
"""Returns the row number of a specified cell."""
|
|
raise NotImplementedError()
|
|
|
|
@unimplemented
|
|
def ROWS(range):
|
|
"""Returns the number of rows in a specified array or range."""
|
|
raise NotImplementedError()
|
|
|
|
def SELF_HYPERLINK(label=None, page=None, **kwargs):
|
|
"""
|
|
Creates a link to the current document. All parameters are optional.
|
|
|
|
The returned string is in URL format, optionally preceded by a label and a space
|
|
(the format expected for Grist Text columns with the HyperLink option enabled).
|
|
|
|
A numeric page number can be supplied, which will create a link to the
|
|
specified page. To find the numeric page number you need, visit a page
|
|
and examine its URL for a `/p/NN` part.
|
|
|
|
Any number of arguments of the form `LinkKey_NAME` may be provided, to set
|
|
`user.LinkKey.NAME` values that will be available in access rules. For example,
|
|
if a rule allows users to view rows when `user.LinkKey.Code == rec.Code`,
|
|
we might want to create links with `SELF_HYPERLINK(LinkKey_Code=$Code)`.
|
|
|
|
>>> SELF_HYPERLINK()
|
|
u'https://docs.getgrist.com/sbaltsirg/Example'
|
|
>>> SELF_HYPERLINK(label='doc')
|
|
u'doc https://docs.getgrist.com/sbaltsirg/Example'
|
|
>>> SELF_HYPERLINK(page=2)
|
|
u'https://docs.getgrist.com/sbaltsirg/Example/p/2'
|
|
>>> SELF_HYPERLINK(LinkKey_Code='X1234')
|
|
u'https://docs.getgrist.com/sbaltsirg/Example?Code_=X1234'
|
|
>>> SELF_HYPERLINK(label='order', page=3, LinkKey_Code='X1234', LinkKey_Name='Bi Ngo')
|
|
u'order https://docs.getgrist.com/sbaltsirg/Example/p/3?Code_=X1234&Name_=Bi+Ngo'
|
|
>>> SELF_HYPERLINK(Linky_Link='Link')
|
|
Traceback (most recent call last):
|
|
...
|
|
TypeError: unexpected keyword argument 'Linky_Link' (not of form LinkKey_NAME)
|
|
"""
|
|
txt = os.environ.get('DOC_URL')
|
|
if not txt:
|
|
return None
|
|
txt = six.text_type(txt)
|
|
if page:
|
|
txt += "/p/{}".format(page)
|
|
if kwargs:
|
|
parts = list(urllib_parse.urlparse(txt))
|
|
query = OrderedDict(urllib_parse.parse_qsl(parts[4]))
|
|
for [key, value] in sorted(six.iteritems(kwargs)):
|
|
key_parts = key.split('LinkKey_')
|
|
if len(key_parts) == 2 and key_parts[0] == '':
|
|
query[key_parts[1] + '_'] = value
|
|
else:
|
|
raise TypeError("unexpected keyword argument '{}' (not of form LinkKey_NAME)".format(key))
|
|
parts[4] = urllib_parse.urlencode(query)
|
|
txt = urllib_parse.urlunparse(parts)
|
|
if label:
|
|
txt = u"{} {}".format(label, txt)
|
|
return txt
|
|
|
|
def VLOOKUP(table, **field_value_pairs):
|
|
"""
|
|
Vertical lookup. Searches the given table for a record matching the given `field=value`
|
|
arguments. If multiple records match, returns one of them. If none match, returns the special
|
|
empty record.
|
|
|
|
The returned object is a record whose fields are available using `.field` syntax. For example,
|
|
`VLOOKUP(Employees, EmployeeID=$EmpID).Salary`.
|
|
|
|
Note that `VLOOKUP` isn't commonly needed in Grist, since [Reference columns](col-refs.md) are the
|
|
best way to link data between tables, and allow simple efficient usage such as `$Person.Age`.
|
|
|
|
`VLOOKUP` is exactly quivalent to `table.lookupOne(**field_value_pairs)`. See
|
|
[lookupOne](#lookupone).
|
|
|
|
For example:
|
|
```
|
|
VLOOKUP(People, First_Name="Lewis", Last_Name="Carroll")
|
|
VLOOKUP(People, First_Name="Lewis", Last_Name="Carroll").Age
|
|
```
|
|
"""
|
|
return table.lookupOne(**field_value_pairs)
|
|
|
|
class CONTAINS(namedtuple("CONTAINS", "value")):
|
|
"""
|
|
Use this marker with `Table.lookupRecords` to find records
|
|
where a column contains the given value, e.g:
|
|
|
|
MoviesTable.lookupRecords(genre=CONTAINS("Drama"))
|
|
|
|
will return records in `MoviesTable` where the column `genre`
|
|
is a list or other container such as `["Comedy", "Drama"]`,
|
|
i.e. `"Drama" in $genre`.
|
|
|
|
Note that the column being looked up (e.g. `genre`)
|
|
must have values of a container type such as list, tuple, or set.
|
|
In particular the values mustn't be strings, e.g. `"Comedy-Drama"` won't match
|
|
even though `"Drama" in "Comedy-Drama"` is `True` in Python.
|
|
It also won't match substrings within container elements, e.g. `["Comedy-Drama"]`.
|
|
"""
|
|
# While users should apply this marker to values in queries, internally
|
|
# the marker is moved to the column ID so that the LookupMapColumn knows how to
|
|
# update its index correctly for that column.
|
|
pass
|