orf / datatables

SQLAlchemy->Datatables
MIT License
52 stars 23 forks source link

Search functionality doesn't seem to be implemented #6

Open tahoe opened 8 years ago

tahoe commented 8 years ago

Hi, not to nit pick but I can't see how searching could possibly work here. in the init method you define search_func as a lambda that takes two arguments and just returns the first. I don't see an intuitive way to add a search function so is this a placeholder?

Also, I tried using this with Flask-restful and there is no request.GET method and the multidict that request.values returns isn't parsable in my testing.

I have modified the code so that it works with Flask-restful. It requires the import

from querystring_parser import parser

and where you pass request.GET in your example I am passing the following

parser.parse(request.query_string)

To get this to work I removed the query_into_dict method and just passed in the vars like:

#columns = self.query_into_dict("columns")
#ordering = self.query_into_dict("order")
#search = self.query_into_dict("search")

columns = self.params["columns"]
ordering = self.params["order"]
search = self.params["search"]

still working out how to get searching working though.

tahoe commented 8 years ago

I had to make a few more modifications. I can provide a diff if this is interesting to you at all...

orf commented 8 years ago

Hey there, For the search issue you are facing then all you have to do is call table.searchable() and pass a callable. It doesn't have to be a lambda, but it needs to return a SQLAlchemy QuerySet (the default lambda just returns the QuerySet it is given, so no searching occurs).

In the readme this example is given: table.searchable(lambda queryset, user_input: perform_some_search(queryset, user_input)) (but I see now it could just be table.searchable(perform_some_search)). A naieve implementation could just use an ilike query to filter. Unfortunately the search is global, so no per-column searching is implemented.

Also, I tried using this with Flask-restful and there is no request.GET method and the multidict that request.values returns isn't parsable in my testing.

Could you elaborate on that? The first argument to DataTable should be a dictionary, it doesn't have to be a MultiDict as far as I can remember, but what do you mean it's not parsable? The keys just need to be the arguments the DataTables widget gives (ensure you set serverSide and processing to true to make datatables use the new API format). In your example you give self.params["search"], but DataTables doesn't pass a plain order argument, it should be something like order[1][key_or_something].

tahoe commented 8 years ago

Thanks, I'll double check the values for serverSide and processing. I used an example query my partner was using against a php API.

On the self.params["search"] I am using that parser.parse(request.query_string) and it puts the columns[n][extra] into a sortable dict with a parent key as "columns". It eliminates the whole regex part too and may be more globally usable on a framework basis. Here is what the output looks like: {u'columns': {0: {u'data': u'0', u'name': u'', u'orderable': u'true', u'search': {u'regex': u'false', u'value': u''}, u'searchable': u'true'}, 1: {u'data': u'1', u'name': u'', u'orderable': u'true', u'search': {u'regex': u'false', u'value': u''}, u'searchable': u'true'}}, u'draw': u'1', u'length': u'2', u'order': {0: {u'column': u'0', u'dir': u'asc'}}, u'search': {u'regex': u'false', u'value': u'20'}, u'start': u'1'}

Of course I don't have names and stuff set. If, using Flask, I pass the request.values, which looks like this: CombinedMultiDict([ImmutableMultiDict([('draw', u'1'), ('columns[0][data]', u'0'), ('columns[1][name]', u''), ('columns[1][orderable]', u'true'), ('columns[2][orderable]', u'true'), ('order[0][dir]', u'desc'), ('columns[1][searchable]', u'true'), ('columns[1][search][regex]', u'false'), ('columns[0][search][value]', u''), ('columns[2][searchable]', u'true'), ('columns[0][search][regex]', u'false'), ('start', u'0'), ('columns[0][searchable]', u'true'), ('columns[2][search][value]', u''), ('columns[2][search][regex]', u'false'), ('columns[1][data]', u'1'), ('columns[0][orderable]', u'true'), ('order[0][column]', u'1'), ('columns[0][name]', u''), ('columns[2][data]', u'2'), ('search[value]', u''), ('search[regex]', u'false'), ('columns[1][search][value]', u'11'), ('columns[2][name]', u''), ('length', u'20')]), ImmutableMultiDict([])])

I get this: ... File "/home/dennis/projects/schema-api/libs/datatables.py", line 106, in json return self._json() File "/home/dennis/projects/schema-api/libs/datatables.py", line 150, in _json column = self.columns_dict[column_name] KeyError: 1

If I use Flask's request.query_string which is just the whole encoded url parameters, it doesn't work either. But if I use that parser library I mentioned and call parser.parse(request.query_string), it parses it into a dict that I pasted above which works at least to perform the query and return the correct results back to datatables but won't allow me to sort.

Anyway, tl;dr... I'll make sure the client side is doing what it's supposed to. Hopefully I just have to take my foot out of my mouth.

tahoe commented 8 years ago

I apologize for the comments regarding searching. I was looking at the documentation on pypi documentation for 0.4.1 for some reason. Not sure how I got to an older version.

orf commented 8 years ago

Hmm, that querystring_parser library looks really nice. In any case I'm up for adding it in and replacing our hacky regex thing, if you've already done this then feel free to open a merge request and I'll take a look?

tahoe commented 8 years ago

I had to make another couple of small changes to get my other changes working properly. Especially the lambda part and call to it from the get_value method The reason I needed to change it was because I am looping through 100 SA table classes that I retrieve from my Base.myclasses() method I built and I'm having to dynamically add the lambda based on the column type and stuff so I needed the lambda input to look like

    (col, col, lambda i: "{}".format(i))

and then in the get_value method on the DataTable object:

    if key.filter is not None:
        r = key.filter(getattr(instance, attr))

Let me know if this makes sense. It seems to me this is more flexible as it allows me to do a loop like I am or for manually doing it like in your example you can still provide the same info manually and it should work.