def get(self, request, view_id, field_options): """ Lists all the rows of a grid view, paginated either by a page or offset/limit. If the limit get parameter is provided the limit/offset pagination will be used else the page number pagination. Optionally the field options can also be included in the response if the the `field_options` are provided in the include GET parameter. """ search = request.GET.get('search') view_handler = ViewHandler() view = view_handler.get_view(view_id, GridView) view.table.database.group.has_user(request.user, raise_error=True, allow_if_template=True) model = view.table.get_model() queryset = model.objects.all().enhance_by_fields() # Applies the view filters and sortings to the queryset if there are any. queryset = view_handler.apply_filters(view, queryset) queryset = view_handler.apply_sorting(view, queryset) if search: queryset = queryset.search_all_fields(search) if 'count' in request.GET: return Response({'count': queryset.count()}) if LimitOffsetPagination.limit_query_param in request.GET: paginator = LimitOffsetPagination() else: paginator = PageNumberPagination() page = paginator.paginate_queryset(queryset, request, self) serializer_class = get_row_serializer_class(model, RowSerializer, is_response=True) serializer = serializer_class(page, many=True) response = paginator.get_paginated_response(serializer.data) if field_options: # The serializer has the GridViewFieldOptionsField which fetches the # field options from the database and creates them if they don't exist, # but when added to the context the fields don't have to be fetched from # the database again when checking if they exist. context = { 'fields': [o['field'] for o in model._field_objects.values()] } serialized_view = GridViewSerializer(view, context=context).data response.data['field_options'] = serialized_view['field_options'] return response
def row_updated(sender, row, user, table, model, **kwargs): table_page_type = page_registry.get('table') transaction.on_commit(lambda: table_page_type.broadcast( { 'type': 'row_updated', 'table_id': table.id, 'row': get_row_serializer_class(model, RowSerializer, is_response=True) (row).data }, getattr(user, 'web_socket_id', None), table_id=table.id))
def post(self, request, view_id, data): """ Row filter endpoint that only lists the requested rows and optionally only the requested fields. """ view = ViewHandler().get_view(request.user, view_id, GridView) model = view.table.get_model(field_ids=data['field_ids']) results = model.objects.filter(pk__in=data['row_ids']) serializer_class = get_row_serializer_class(model, RowSerializer, is_response=True) serializer = serializer_class(results, many=True) return Response(serializer.data)
def row_created(sender, row, before, user, table, model, **kwargs): table_page_type = page_registry.get("table") transaction.on_commit( lambda: table_page_type.broadcast( { "type": "row_created", "table_id": table.id, "row": get_row_serializer_class(model, RowSerializer, is_response=True)( row ).data, "before_row_id": before.id if before else None, }, getattr(user, "web_socket_id", None), table_id=table.id, ) )
def row_updated(sender, row, user, table, model, before_return, **kwargs): table_page_type = page_registry.get("table") transaction.on_commit( lambda: table_page_type.broadcast( { "type": "row_updated", "table_id": table.id, # The web-frontend expects a serialized version of the row before it # was updated in order the estimate what position the row had in the # view. "row_before_update": dict(before_return)[before_row_update], "row": get_row_serializer_class(model, RowSerializer, is_response=True)( row ).data, }, getattr(user, "web_socket_id", None), table_id=table.id, ) )
def get(self, request, view_id, field_options): """ Lists all the rows of a grid view, paginated either by a page or offset/limit. If the limit get parameter is provided the limit/offset pagination will be used else the page number pagination. Optionally the field options can also be included in the response if the the `field_options` are provided in the includes GET parameter. """ view = ViewHandler().get_view(request.user, view_id, GridView) model = view.table.get_model() queryset = model.objects.all().enhance_by_fields().order_by('id') if LimitOffsetPagination.limit_query_param in request.GET: paginator = LimitOffsetPagination() else: paginator = PageNumberPagination() page = paginator.paginate_queryset(queryset, request, self) serializer_class = get_row_serializer_class(model, RowSerializer, is_response=True) serializer = serializer_class(page, many=True) response = paginator.get_paginated_response(serializer.data) if field_options: # The serializer has the GridViewFieldOptionsField which fetches the # field options from the database and creates them if they don't exist, # but when added to the context the fields don't have to be fetched from # the database again when checking if they exist. context = { 'fields': [o['field'] for o in model._field_objects.values()] } response.data.update( **GridViewSerializer(view, context=context).data) return response
def test_get_table_serializer(data_fixture): table = data_fixture.create_database_table(name='Cars') table_2 = data_fixture.create_database_table() data_fixture.create_text_field(table=table, order=0, name='Color', text_default='white') data_fixture.create_number_field(table=table, order=1, name='Horsepower') data_fixture.create_boolean_field(table=table, order=2, name='For sale') data_fixture.create_number_field(table=table, order=2, name='Price', number_type='DECIMAL', number_negative=True, number_decimal_places=2) model = table.get_model(attribute_names=True) serializer_class = get_row_serializer_class(model=model) # expect the result to be empty if not provided serializer_instance = serializer_class(data={}) assert serializer_instance.is_valid() assert serializer_instance.data == { 'color': 'white', 'horsepower': None, 'for_sale': False, 'price': None } # text field serializer_instance = serializer_class(data={'color': 'Green'}) assert serializer_instance.is_valid() assert serializer_instance.data['color'] == 'Green' serializer_instance = serializer_class(data={'color': 123}) assert serializer_instance.is_valid() assert serializer_instance.data['color'] == '123' serializer_instance = serializer_class(data={'color': None}) assert serializer_instance.is_valid() assert serializer_instance.data['color'] == None # number field serializer_instance = serializer_class(data={'horsepower': 120}) assert serializer_instance.is_valid() assert serializer_instance.data['horsepower'] == 120 serializer_instance = serializer_class(data={'horsepower': None}) assert serializer_instance.is_valid() assert serializer_instance.data['horsepower'] == None serializer_instance = serializer_class(data={'horsepower': 'abc'}) assert not serializer_instance.is_valid() assert len(serializer_instance.errors['horsepower']) == 1 serializer_instance = serializer_class(data={'horsepower': -1}) assert not serializer_instance.is_valid() assert len(serializer_instance.errors['horsepower']) == 1 # boolean field serializer_instance = serializer_class(data={'for_sale': True}) assert serializer_instance.is_valid() assert serializer_instance.data['for_sale'] == True serializer_instance = serializer_class(data={'for_sale': False}) assert serializer_instance.is_valid() assert serializer_instance.data['for_sale'] == False serializer_instance = serializer_class(data={'for_sale': None}) assert not serializer_instance.is_valid() assert len(serializer_instance.errors['for_sale']) == 1 serializer_instance = serializer_class(data={'for_sale': 'abc'}) assert not serializer_instance.is_valid() assert len(serializer_instance.errors['for_sale']) == 1 # price field serializer_instance = serializer_class(data={'price': 120}) assert serializer_instance.is_valid() assert serializer_instance.data['price'] == '120.00' serializer_instance = serializer_class(data={'price': '-10.22'}) assert serializer_instance.is_valid() assert serializer_instance.data['price'] == '-10.22' serializer_instance = serializer_class(data={'price': 'abc'}) assert not serializer_instance.is_valid() assert len(serializer_instance.errors['price']) == 1 serializer_instance = serializer_class(data={'price': None}) assert serializer_instance.is_valid() assert serializer_instance.data['price'] == None # not existing value serializer_instance = serializer_class(data={'NOT_EXISTING': True}) assert serializer_instance.is_valid() assert serializer_instance.data == { 'color': 'white', 'horsepower': None, 'for_sale': False, 'price': None } # all fields serializer_instance = serializer_class(data={ 'color': 'green', 'horsepower': 120, 'for_sale': True, 'price': 120.22 }) assert serializer_instance.is_valid() assert serializer_instance.data == { 'color': 'green', 'horsepower': 120, 'for_sale': True, 'price': '120.22' } # adding an extra field and only use that one. price_field = data_fixture.create_number_field(table=table_2, order=0, name='Sale price', number_type='DECIMAL', number_decimal_places=3, number_negative=True) model = table.get_model(fields=[price_field], field_ids=[]) serializer_class = get_row_serializer_class(model=model) serializer_instance = serializer_class( data={f'field_{price_field.id}': 12.22}) assert serializer_instance.is_valid() assert serializer_instance.data == {f'field_{price_field.id}': '12.220'} serializer_instance = serializer_class( data={f'field_{price_field.id}': -10.02}) assert serializer_instance.is_valid() assert serializer_instance.data == {f'field_{price_field.id}': '-10.020'} serializer_instance = serializer_class( data={f'field_{price_field.id}': 'abc'}) assert not serializer_instance.is_valid() assert len(serializer_instance.errors[f'field_{price_field.id}']) == 1
def test_primary_single_select_field_with_link_row_field( api_client, data_fixture, django_assert_num_queries): """ We expect the relation to a table that has a single select field to work. """ user, token = data_fixture.create_user_and_token() database = data_fixture.create_database_application(user=user, name='Placeholder') example_table = data_fixture.create_database_table(name='Example', database=database) customers_table = data_fixture.create_database_table(name='Customers', database=database) field_handler = FieldHandler() row_handler = RowHandler() data_fixture.create_text_field(name='Name', table=example_table, primary=True) customers_primary = field_handler.create_field(user=user, table=customers_table, type_name='single_select', select_options=[{ 'value': 'Option 1', 'color': 'red' }, { 'value': 'Option 2', 'color': 'blue' }, { 'value': 'Option 3', 'color': 'orange' }], primary=True) link_row_field = field_handler.create_field(user=user, table=example_table, type_name='link_row', link_row_table=customers_table) select_options = customers_primary.select_options.all() customers_row_1 = row_handler.create_row( user=user, table=customers_table, values={f'field_{customers_primary.id}': select_options[0].id}) customers_row_2 = row_handler.create_row( user=user, table=customers_table, values={f'field_{customers_primary.id}': select_options[1].id}) customers_row_3 = row_handler.create_row( user=user, table=customers_table, values={f'field_{customers_primary.id}': select_options[2].id}) row_handler.create_row(user, table=example_table, values={ f'field_{link_row_field.id}': [customers_row_1.id, customers_row_2.id] }) row_handler.create_row( user, table=example_table, values={f'field_{link_row_field.id}': [customers_row_1.id]}) row_handler.create_row( user, table=example_table, values={f'field_{link_row_field.id}': [customers_row_3.id]}) model = example_table.get_model() queryset = model.objects.all().enhance_by_fields() serializer_class = get_row_serializer_class(model, RowSerializer, is_response=True) with django_assert_num_queries(3): serializer = serializer_class(queryset, many=True) serializer.data response = api_client.get(reverse('api:database:rows:list', kwargs={'table_id': example_table.id}), format='json', HTTP_AUTHORIZATION=f'JWT {token}') response_json = response.json() assert (response_json['results'][0][f'field_{link_row_field.id}'][0] ['value'] == 'Option 1') assert (response_json['results'][0][f'field_{link_row_field.id}'][1] ['value'] == 'Option 2') assert (response_json['results'][1][f'field_{link_row_field.id}'][0] ['value'] == 'Option 1') assert (response_json['results'][2][f'field_{link_row_field.id}'][0] ['value'] == 'Option 3')
def test_primary_single_select_field_with_link_row_field( api_client, data_fixture, django_assert_num_queries): """ We expect the relation to a table that has a single select field to work. """ user, token = data_fixture.create_user_and_token() database = data_fixture.create_database_application(user=user, name="Placeholder") example_table = data_fixture.create_database_table(name="Example", database=database) customers_table = data_fixture.create_database_table(name="Customers", database=database) field_handler = FieldHandler() row_handler = RowHandler() data_fixture.create_text_field(name="Name", table=example_table, primary=True) customers_primary = field_handler.create_field( user=user, table=customers_table, type_name="single_select", select_options=[ { "value": "Option 1", "color": "red" }, { "value": "Option 2", "color": "blue" }, { "value": "Option 3", "color": "orange" }, ], primary=True, ) link_row_field = field_handler.create_field( user=user, table=example_table, type_name="link_row", link_row_table=customers_table, ) select_options = customers_primary.select_options.all() customers_row_1 = row_handler.create_row( user=user, table=customers_table, values={f"field_{customers_primary.id}": select_options[0].id}, ) customers_row_2 = row_handler.create_row( user=user, table=customers_table, values={f"field_{customers_primary.id}": select_options[1].id}, ) customers_row_3 = row_handler.create_row( user=user, table=customers_table, values={f"field_{customers_primary.id}": select_options[2].id}, ) row_handler.create_row( user, table=example_table, values={ f"field_{link_row_field.id}": [customers_row_1.id, customers_row_2.id] }, ) row_handler.create_row( user, table=example_table, values={f"field_{link_row_field.id}": [customers_row_1.id]}, ) row_handler.create_row( user, table=example_table, values={f"field_{link_row_field.id}": [customers_row_3.id]}, ) model = example_table.get_model() queryset = model.objects.all().enhance_by_fields() serializer_class = get_row_serializer_class(model, RowSerializer, is_response=True) with django_assert_num_queries(3): serializer = serializer_class(queryset, many=True) serializer.data response = api_client.get( reverse("api:database:rows:list", kwargs={"table_id": example_table.id}), format="json", HTTP_AUTHORIZATION=f"JWT {token}", ) response_json = response.json() assert (response_json["results"][0][f"field_{link_row_field.id}"][0] ["value"] == "Option 1") assert (response_json["results"][0][f"field_{link_row_field.id}"][1] ["value"] == "Option 2") assert (response_json["results"][1][f"field_{link_row_field.id}"][0] ["value"] == "Option 1") assert (response_json["results"][2][f"field_{link_row_field.id}"][0] ["value"] == "Option 3")
def test_get_table_serializer(data_fixture): table = data_fixture.create_database_table(name="Cars") table_2 = data_fixture.create_database_table() text_field = data_fixture.create_text_field(table=table, order=0, name="Color", text_default="white") data_fixture.create_number_field(table=table, order=1, name="Horsepower") data_fixture.create_boolean_field(table=table, order=3, name="For sale") data_fixture.create_number_field( table=table, order=4, name="Price", number_type="DECIMAL", number_negative=True, number_decimal_places=2, ) model = table.get_model(attribute_names=True) serializer_class = get_row_serializer_class(model=model) # expect the result to be empty if not provided serializer_instance = serializer_class(data={}) assert serializer_instance.is_valid() assert serializer_instance.data == { "color": "white", "horsepower": None, "for_sale": False, "price": None, } # text field serializer_instance = serializer_class(data={"color": "Green"}) assert serializer_instance.is_valid() assert serializer_instance.data["color"] == "Green" serializer_instance = serializer_class(data={"color": 123}) assert serializer_instance.is_valid() assert serializer_instance.data["color"] == "123" serializer_instance = serializer_class(data={"color": None}) assert serializer_instance.is_valid() assert serializer_instance.data["color"] is None # number field serializer_instance = serializer_class(data={"horsepower": 120}) assert serializer_instance.is_valid() assert serializer_instance.data["horsepower"] == "120" serializer_instance = serializer_class( data={ "horsepower": 99999999999999999999999999999999999999999999999999 }) assert serializer_instance.is_valid() assert (serializer_instance.data["horsepower"] == "99999999999999999999999999999999999999999999999999") serializer_instance = serializer_class( data={ "horsepower": 999999999999999999999999999999999999999999999999999 }) assert not serializer_instance.is_valid() serializer_instance = serializer_class(data={"horsepower": None}) assert serializer_instance.is_valid() assert serializer_instance.data["horsepower"] is None serializer_instance = serializer_class(data={"horsepower": "abc"}) assert not serializer_instance.is_valid() assert len(serializer_instance.errors["horsepower"]) == 1 serializer_instance = serializer_class(data={"horsepower": -1}) assert not serializer_instance.is_valid() assert len(serializer_instance.errors["horsepower"]) == 1 # boolean field serializer_instance = serializer_class(data={"for_sale": True}) assert serializer_instance.is_valid() assert serializer_instance.data["for_sale"] is True serializer_instance = serializer_class(data={"for_sale": False}) assert serializer_instance.is_valid() assert serializer_instance.data["for_sale"] is False serializer_instance = serializer_class(data={"for_sale": None}) assert not serializer_instance.is_valid() assert len(serializer_instance.errors["for_sale"]) == 1 serializer_instance = serializer_class(data={"for_sale": "abc"}) assert not serializer_instance.is_valid() assert len(serializer_instance.errors["for_sale"]) == 1 # price field serializer_instance = serializer_class(data={"price": 120}) assert serializer_instance.is_valid() assert serializer_instance.data["price"] == "120.00" serializer_instance = serializer_class(data={"price": "-10.22"}) assert serializer_instance.is_valid() assert serializer_instance.data["price"] == "-10.22" serializer_instance = serializer_class(data={"price": "abc"}) assert not serializer_instance.is_valid() assert len(serializer_instance.errors["price"]) == 1 serializer_instance = serializer_class(data={"price": None}) assert serializer_instance.is_valid() assert serializer_instance.data["price"] is None # not existing value serializer_instance = serializer_class(data={"NOT_EXISTING": True}) assert serializer_instance.is_valid() assert serializer_instance.data == { "color": "white", "horsepower": None, "for_sale": False, "price": None, } # all fields serializer_instance = serializer_class(data={ "color": "green", "horsepower": 120, "for_sale": True, "price": 120.22 }) assert serializer_instance.is_valid() assert serializer_instance.data == { "color": "green", "horsepower": "120", "for_sale": True, "price": "120.22", } # adding an extra field and only use that one. price_field = data_fixture.create_number_field( table=table_2, order=0, name="Sale price", number_type="DECIMAL", number_decimal_places=3, number_negative=True, ) model = table.get_model(fields=[price_field], field_ids=[]) serializer_class = get_row_serializer_class(model=model) serializer_instance = serializer_class( data={f"field_{price_field.id}": 12.22}) assert serializer_instance.is_valid() assert serializer_instance.data == {f"field_{price_field.id}": "12.220"} serializer_instance = serializer_class( data={f"field_{price_field.id}": -10.02}) assert serializer_instance.is_valid() assert serializer_instance.data == {f"field_{price_field.id}": "-10.020"} serializer_instance = serializer_class( data={f"field_{price_field.id}": "abc"}) assert not serializer_instance.is_valid() assert len(serializer_instance.errors[f"field_{price_field.id}"]) == 1 model = table.get_model(attribute_names=True) serializer_class = get_row_serializer_class(model=model, field_ids=[text_field.id]) serializer_instance = serializer_class(data={}) assert serializer_instance.is_valid() assert serializer_instance.data == {"color": "white"}
def before_row_delete(sender, row, user, table, model, **kwargs): # Generate a serialized version of the row before it is deleted. The # `row_deleted` receiver needs this serialized version because it can't serialize # the row after is has been deleted. return get_row_serializer_class(model, RowSerializer, is_response=True)(row).data