def search_all_fields(self, search): """ Performs a very broad search across all supported fields with the given search query. If the primary key value matches then that result will be returned otherwise all field types other than link row and boolean fields are currently searched. :param search: The search query. :type search: str :return: The queryset containing the search queries. :rtype: QuerySet """ filter_builder = FilterBuilder(filter_type=FILTER_TYPE_OR).filter( Q(id__contains=search) ) for field_object in self.model._field_objects.values(): field_name = field_object['name'] model_field = self.model._meta.get_field(field_name) sub_filter = field_object['type'].contains_query( field_name, search, model_field, field_object['field'] ) filter_builder.filter(sub_filter) return filter_builder.apply_to_queryset(self)
def apply_filters(self, view, queryset): """ Applies the view's filter to the given queryset. :param view: The view where to fetch the fields from. :type view: View :param queryset: The queryset where the filters need to be applied to. :type queryset: QuerySet :raises ValueError: When the queryset's model is not a table model or if the table model does not contain the one of the fields. :return: The queryset where the filters have been applied to. :type: QuerySet """ model = queryset.model # If the model does not have the `_field_objects` property then it is not a # generated table model which is not supported. if not hasattr(model, "_field_objects"): raise ValueError("A queryset of the table model is required.") # If the filter are disabled we don't have to do anything with the queryset. if view.filters_disabled: return queryset filter_builder = FilterBuilder(filter_type=view.filter_type) for view_filter in view.viewfilter_set.all(): # If the to be filtered field is not present in the `_field_objects` we # cannot filter so we raise a ValueError. if view_filter.field_id not in model._field_objects: raise ValueError( f"The table model does not contain field " f"{view_filter.field_id}." ) field_object = model._field_objects[view_filter.field_id] field_name = field_object["name"] model_field = model._meta.get_field(field_name) view_filter_type = view_filter_type_registry.get(view_filter.type) filter_builder.filter( view_filter_type.get_filter( field_name, view_filter.value, model_field, field_object["field"] ) ) return filter_builder.apply_to_queryset(queryset)
def test_building_filter_with_and_type_ands_all_provided_qs_together( data_fixture): user = data_fixture.create_user() table = data_fixture.create_database_table(user=user) text_field = data_fixture.create_text_field(table=table, order=1, name="name") bool_field = data_fixture.create_boolean_field(table=table, order=2, name="is_active") model = table.get_model() row_1 = model.objects.create(**{ f"field_{text_field.id}": "name", f"field_{bool_field.id}": True }) model.objects.create(**{ f"field_{text_field.id}": "name", f"field_{bool_field.id}": False }) builder = FilterBuilder(filter_type=FILTER_TYPE_AND) builder.filter(Q(**{f"field_{text_field.id}": "name"})) builder.filter(Q(**{f"field_{bool_field.id}": True})) queryset = builder.apply_to_queryset(model.objects) assert queryset.count() == 1 assert row_1 in queryset
def test_can_invert_an_annotated_q(data_fixture): user = data_fixture.create_user() table = data_fixture.create_database_table(user=user) text_field = data_fixture.create_text_field(table=table, order=1, name="name") another_text_field = data_fixture.create_text_field(table=table, order=2, name="surname") model = table.get_model() model.objects.create( **{ f"field_{text_field.id}": "name", f"field_{another_text_field.id}": "other" }) row_2 = model.objects.create( **{ f"field_{text_field.id}": "eman", f"field_{another_text_field.id}": "extra" }) row_3 = model.objects.create( **{ f"field_{text_field.id}": "not_name", f"field_{another_text_field.id}": "not_other", }) builder = FilterBuilder(filter_type=FILTER_TYPE_AND) q_to_invert = AnnotatedQ( annotation={"reversed_name": Reverse(f"field_{text_field.id}")}, q={f"reversed_name": "eman"}, ) builder.filter(~q_to_invert) queryset = builder.apply_to_queryset(model.objects) assert queryset.count() == 2 assert row_2 in queryset assert row_3 in queryset
def test_can_invert_an_annotated_q(data_fixture): user = data_fixture.create_user() table = data_fixture.create_database_table(user=user) text_field = data_fixture.create_text_field(table=table, order=1, name='name') another_text_field = data_fixture.create_text_field(table=table, order=2, name='surname') model = table.get_model() model.objects.create( **{ f'field_{text_field.id}': 'name', f'field_{another_text_field.id}': 'other' }) row_2 = model.objects.create( **{ f'field_{text_field.id}': 'eman', f'field_{another_text_field.id}': 'extra' }) row_3 = model.objects.create( **{ f'field_{text_field.id}': 'not_name', f'field_{another_text_field.id}': 'not_other' }) builder = FilterBuilder(filter_type=FILTER_TYPE_AND) q_to_invert = AnnotatedQ( annotation={'reversed_name': Reverse(f'field_{text_field.id}')}, q={f'reversed_name': 'eman'}) builder.filter(~q_to_invert) queryset = builder.apply_to_queryset(model.objects) assert queryset.count() == 2 assert row_2 in queryset assert row_3 in queryset
def test_building_filter_with_annotated_qs_annotates_prior_to_filter( data_fixture): user = data_fixture.create_user() table = data_fixture.create_database_table(user=user) text_field = data_fixture.create_text_field(table=table, order=1, name="name") another_text_field = data_fixture.create_text_field(table=table, order=2, name="surname") model = table.get_model() row_1 = model.objects.create( **{ f"field_{text_field.id}": "name", f"field_{another_text_field.id}": "other" }) model.objects.create( **{ f"field_{text_field.id}": "eman", f"field_{another_text_field.id}": "extra" }) model.objects.create( **{ f"field_{text_field.id}": "not_name", f"field_{another_text_field.id}": "not_other", }) builder = FilterBuilder(filter_type=FILTER_TYPE_OR) builder.filter( AnnotatedQ( annotation={"reversed_name": Reverse(f"field_{text_field.id}")}, q={f"field_{text_field.id}": "name"}, )) builder.filter(Q(**{f"reversed_name": "eman"})) queryset = builder.apply_to_queryset(model.objects) assert queryset.count() == 1 assert row_1 in queryset
def test_building_filter_with_annotated_qs_annotates_prior_to_filter( data_fixture): user = data_fixture.create_user() table = data_fixture.create_database_table(user=user) text_field = data_fixture.create_text_field(table=table, order=1, name='name') another_text_field = data_fixture.create_text_field(table=table, order=2, name='surname') model = table.get_model() row_1 = model.objects.create( **{ f'field_{text_field.id}': 'name', f'field_{another_text_field.id}': 'other' }) model.objects.create( **{ f'field_{text_field.id}': 'eman', f'field_{another_text_field.id}': 'extra' }) model.objects.create( **{ f'field_{text_field.id}': 'not_name', f'field_{another_text_field.id}': 'not_other' }) builder = FilterBuilder(filter_type=FILTER_TYPE_OR) builder.filter( AnnotatedQ( annotation={'reversed_name': Reverse(f'field_{text_field.id}')}, q={f'field_{text_field.id}': 'name'})) builder.filter(Q(**{f'reversed_name': 'eman'})) queryset = builder.apply_to_queryset(model.objects) assert queryset.count() == 1 assert row_1 in queryset
def test_building_filter_with_or_type_ors_all_provided_qs_together( data_fixture): user = data_fixture.create_user() table = data_fixture.create_database_table(user=user) text_field = data_fixture.create_text_field(table=table, order=1, name="name") another_text_field = data_fixture.create_text_field(table=table, order=2, name="surname") model = table.get_model() row_1 = model.objects.create( **{ f"field_{text_field.id}": "name", f"field_{another_text_field.id}": "other" }) row_2 = model.objects.create( **{ f"field_{text_field.id}": "not_name", f"field_{another_text_field.id}": "extra", }) model.objects.create( **{ f"field_{text_field.id}": "not_name", f"field_{another_text_field.id}": "not_other", }) builder = FilterBuilder(filter_type=FILTER_TYPE_OR) builder.filter(Q(**{f"field_{text_field.id}": "name"})) builder.filter(Q(**{f"field_{another_text_field.id}": "extra"})) queryset = builder.apply_to_queryset(model.objects) assert queryset.count() == 2 assert row_1 in queryset assert row_2 in queryset
def filter_by_fields_object(self, filter_object, filter_type=FILTER_TYPE_AND): """ Filters the query by the provided filters in the filter_object. The following format `filter__field_{id}__{view_filter_type}` is expected as key and multiple values can be provided as a list containing strings. Only the view filter types are allowed. Example: { 'filter__field_{id}__{view_filter_type}': {value}. } :param filter_object: The object containing the field and filter type as key and the filter value as value. :type filter_object: object :param filter_type: Indicates if the provided filters are in an AND or OR statement. :type filter_type: str :raises ValueError: Raised when the provided filer_type isn't AND or OR. :raises FilterFieldNotFound: Raised when the provided field isn't found in the model. :raises ViewFilterTypeDoesNotExist: when the view filter type doesn't exist. :raises ViewFilterTypeNotAllowedForField: when the view filter type isn't compatible with field type. :return: The filtered queryset. :rtype: QuerySet """ if filter_type not in [FILTER_TYPE_AND, FILTER_TYPE_OR]: raise ValueError(f'Unknown filter type {filter_type}.') filter_builder = FilterBuilder(filter_type=filter_type) for key, values in filter_object.items(): matches = deconstruct_filter_key_regex.match(key) if not matches: continue field_id = int(matches[1]) if field_id not in self.model._field_objects: raise FilterFieldNotFound( field_id, f'Field {field_id} does not exist.' ) field_object = self.model._field_objects[field_id] field_name = field_object['name'] field_type = field_object['type'].type model_field = self.model._meta.get_field(field_name) view_filter_type = view_filter_type_registry.get(matches[2]) if field_type not in view_filter_type.compatible_field_types: raise ViewFilterTypeNotAllowedForField( matches[2], field_type, ) if not isinstance(values, list): values = [values] for value in values: filter_builder.filter( view_filter_type.get_filter( field_name, value, model_field, field_object['field'] ) ) return filter_builder.apply_to_queryset(self)