1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-25 05:21:30 +00:00

Merge branch 'add_columns_fill_command' into 'develop'

Add new optional fill_table parameter --add-column which creates a column of...

Closes 

See merge request 
This commit is contained in:
Nigel Gott 2021-04-13 15:16:31 +00:00
commit 0b75d87d29
5 changed files with 190 additions and 38 deletions
backend
src/baserow/contrib/database
fields
management/commands
tests/baserow/contrib/database
changelog.md

View file

@ -0,0 +1,118 @@
import itertools
from typing import Dict, Any, List, Union, Tuple
from baserow.contrib.database.fields.models import NUMBER_TYPE_CHOICES
from baserow.contrib.database.fields.registries import field_type_registry
FieldKeywordArgPossibilities = Dict[str, Union[List[str], str]]
def map_dict_key_into_values(
field_type_kwargs: FieldKeywordArgPossibilities,
) -> Dict[str, List[Tuple[str, str]]]:
"""
Given a dictionary in the form of::
{
'key': 'value',
'key2': ['value1', 'value2']
}
this function will convert the value of each key to be a list of tuples of the
key and the value returning in the case of the above example::
{
'key': [('key', 'value')],
'key2': [('key2', 'value1'), ('key2', 'value2')]
}
"""
pairs_dict = {}
for name, options in field_type_kwargs.items():
pairs_dict[name] = []
if not isinstance(options, list):
options = [options]
for option in options:
pairs_dict[name].append((name, option))
return pairs_dict
def construct_all_possible_kwargs_for_field(
field_type_kwargs: FieldKeywordArgPossibilities,
) -> List[Dict[str, Any]]:
"""
Given a dictionary containing for each field type a dictionary of that
field types kwargs and all of their associated possible values::
{
'field_type_name' : {
'a_kwarg_for_this_field_type' : ['optionOne', 'optionTwo'],
'other_kwarg_for_this_field_type' : [True, False],
'final_kwarg_for_this_field_type' : 'constant'
}
}
This function will create a list of instantiated kwargs per field type, the list
will contain every possible combination of the supplied possible values for the
fields kwargs. For example given the dict above this would be the result::
{
'field_type_name' : [
{
'a_kwarg_for_this_field_type' : 'optionOne'
'other_kwarg_for_this_field_type' : True,
'final_kwarg_for_this_field_type' : 'constant'
},
{
'a_kwarg_for_this_field_type' : 'optionTwo'
'other_kwarg_for_this_field_type' : True,
'final_kwarg_for_this_field_type' : 'constant'
},
{
'a_kwarg_for_this_field_type' : 'optionOne'
'other_kwarg_for_this_field_type' : False,
'final_kwarg_for_this_field_type' : 'constant'
},
{
'a_kwarg_for_this_field_type' : 'optionTwo'
'other_kwarg_for_this_field_type' : False,
'final_kwarg_for_this_field_type' : 'constant'
},
]
}
"""
dict_of_kwarg_value_pairs = map_dict_key_into_values(field_type_kwargs)
all_possible_kwargs = [
dict(pairwise_args)
for pairwise_args in itertools.product(*dict_of_kwarg_value_pairs.values())
]
return all_possible_kwargs
def construct_all_possible_field_kwargs(link_table) -> Dict[str, List[Dict[str, Any]]]:
"""
Some baserow field types have multiple different 'modes' which result in
different different database columns and modes of operation being
created. This function creates a dictionary of field type to a list of
kwarg dicts, one for each interesting possible 'subtype' of the field.
"""
extra_kwargs_for_type = {
"date": {
"date_include_time": [True, False],
},
"number": {
"number_type": [number_type for number_type, _ in NUMBER_TYPE_CHOICES],
"number_negative": [True, False],
},
"link_row": {"link_row_table": link_table},
}
all_possible_kwargs_per_type = {}
for field_type_name in field_type_registry.get_types():
extra_kwargs = extra_kwargs_for_type.get(field_type_name, {})
all_possible_kwargs = construct_all_possible_kwargs_for_field(extra_kwargs)
all_possible_kwargs_per_type[field_type_name] = all_possible_kwargs
return all_possible_kwargs_per_type

View file

@ -7,6 +7,10 @@ from django.db.models import Max
from faker import Faker
from baserow.contrib.database.fields.field_helpers import (
construct_all_possible_field_kwargs,
)
from baserow.contrib.database.fields.handler import FieldHandler
from baserow.contrib.database.table.models import Table
from baserow.contrib.database.rows.handler import RowHandler
@ -21,6 +25,12 @@ class Command(BaseCommand):
parser.add_argument(
"limit", type=int, help="Amount of rows that need to be " "inserted."
)
parser.add_argument(
"--add-columns",
action="store_true",
help="Add a column for every field type other than link row to the table "
"before populating it.",
)
def handle(self, *args, **options):
table_id = options["table_id"]
@ -37,6 +47,9 @@ class Command(BaseCommand):
)
sys.exit(1)
if "add_columns" in options and options["add_columns"]:
self.create_a_column_for_every_type(table)
model = table.get_model()
# Find out what the highest order is because we want to append the new rows.
@ -69,3 +82,19 @@ class Command(BaseCommand):
getattr(instance, field_name).set(value)
self.stdout.write(self.style.SUCCESS(f"{limit} rows have been inserted."))
@staticmethod
def create_a_column_for_every_type(table):
field_handler = FieldHandler()
all_kwargs_per_type = construct_all_possible_field_kwargs(None)
for field_type_name, all_possible_kwargs in all_kwargs_per_type.items():
if field_type_name == "link_row":
continue
i = 0
for kwargs in all_possible_kwargs:
i = i + 1
postfix = str(i) if len(all_possible_kwargs) > 1 else ""
kwargs["name"] = field_type_name + postfix
field_handler.create_field(
table.database.group.users.first(), table, field_type_name, **kwargs
)

View file

@ -1,4 +1,3 @@
import itertools
from datetime import date
from decimal import Decimal
from unittest.mock import patch
@ -15,6 +14,9 @@ from baserow.contrib.database.fields.exceptions import (
IncompatiblePrimaryFieldTypeError,
CannotChangeFieldType,
)
from baserow.contrib.database.fields.field_helpers import (
construct_all_possible_field_kwargs,
)
from baserow.contrib.database.fields.field_types import TextFieldType, LongTextFieldType
from baserow.contrib.database.fields.handler import FieldHandler
from baserow.contrib.database.fields.models import (
@ -24,33 +26,12 @@ from baserow.contrib.database.fields.models import (
BooleanField,
SelectOption,
LongTextField,
NUMBER_TYPE_CHOICES,
)
from baserow.contrib.database.fields.registries import field_type_registry
from baserow.contrib.database.rows.handler import RowHandler
from baserow.core.exceptions import UserNotInGroup
def dict_to_pairs(field_type_kwargs):
pairs_dict = {}
for name, options in field_type_kwargs.items():
pairs_dict[name] = []
if not isinstance(options, list):
options = [options]
for option in options:
pairs_dict[name].append((name, option))
return pairs_dict
def construct_all_possible_kwargs(field_type_kwargs):
pairs_dict = dict_to_pairs(field_type_kwargs)
args = [
dict(pairwise_args) for pairwise_args in itertools.product(*pairs_dict.values())
]
return args
# You must add --runslow to pytest to run this test, you can do this in intellij by
# editing the run config for this test and adding --runslow to additional args.
@pytest.mark.django_db
@ -82,22 +63,7 @@ def test_can_convert_between_all_fields(data_fixture):
# different conversion behaviour or entirely different database columns being
# created. Here the kwargs which control these modes are enumerated so we can then
# generate every possible type of conversion.
extra_kwargs_for_type = {
"date": {
"date_include_time": [True, False],
},
"number": {
"number_type": [number_type for number_type, _ in NUMBER_TYPE_CHOICES],
"number_negative": [True, False],
},
"link_row": {"link_row_table": link_table},
}
all_possible_kwargs_per_type = {}
for field_type_name in field_type_registry.get_types():
extra_kwargs = extra_kwargs_for_type.get(field_type_name, {})
all_possible_kwargs = construct_all_possible_kwargs(extra_kwargs)
all_possible_kwargs_per_type[field_type_name] = all_possible_kwargs
all_possible_kwargs_per_type = construct_all_possible_field_kwargs(link_table)
i = 1
for field_type_name, all_possible_kwargs in all_possible_kwargs_per_type.items():

View file

@ -78,3 +78,37 @@ def test_fill_table_no_empty_table(data_fixture, test_limit):
# make sure the first row is still the same
assert first_row_value_before == first_row_value_after
assert len(results) == test_limit + row_length_before_random_insert
@pytest.mark.django_db
def test_fill_table_with_add_columns(
data_fixture,
):
"""
Verify that the fill_table command correctly creates columns when passing in
the --add-columns flag.
"""
# create a new empty table with a field
user = data_fixture.create_user()
table = data_fixture.create_database_table(user=user)
text_field = data_fixture.create_text_field(user=user, table=table)
model = table.get_model()
# create data in the previously created field
values = {f"field_{text_field.id}": "Some Text"}
model.objects.create(**values)
results = model.objects.all()
num_rows_before_fill_type = len(results)
num_columns_before_fill_table = table.field_set.count()
# execute the fill_table command
call_command("fill_table", table.id, 10, "--add-columns")
results = model.objects.all()
table.refresh_from_db()
assert len(results) > num_rows_before_fill_type
assert table.field_set.count() > num_columns_before_fill_table

View file

@ -1,5 +1,10 @@
# Changelog
## Unreleased
* Added `--add-columns` flag to the `fill_table` management command. It creates all the
field types before filling the table with random data.
## Released (2021-04-08)
* Added support for importing tables from XML files.