4

I'm trying to add some functionality to give a user the ability to filter a paginated queryset in Django via URL get parameters, and have got this successfully working:

for f in self.request.GET.getlist('f'):
    try:
        k,v = f.split(':', 1)
        queryset = queryset.filter(**{k:v})
    except:
        pass

However, I am hoping to do so in a way that doesn't use try/except blocks. Is there a standard way in django to check if a string is a valid filter parameter?

For example something like:

my_str = "bad_string_not_in_database"
if some_queryset.is_valid_filter_string(my_str):
   some_queryset.filter(**{my_str:100}) 
  • If I understand correctly, you can find info from the link: https://docs.djangoproject.com/en/1.10/topics/db/managers/ – Deniz Kaplan Oct 11 '16 at 21:16
  • I'm not looking to add an extra command to a custom Manager, I want to know if such a way already exists. –  Oct 11 '16 at 21:18
  • maybe hasattr on your model if it is a "first" level filter. – Alexandros B Oct 11 '16 at 21:29
  • Possible duplicate of [Better to 'try' something and catch the exception or test if its possible first to avoid an exception?](http://stackoverflow.com/questions/7604636/better-to-try-something-and-catch-the-exception-or-test-if-its-possible-first) – e4c5 Oct 11 '16 at 23:48
  • @e4c5 That is not even close to being a duplicate. I'm asking if such a test exists in Django, not which is better. –  Oct 11 '16 at 23:49
  • 1
    This is in fact pretty much a duplicate. It's a question of doing an if to find out if the model field exists or doing a try except. Those are the only possible ways of achieving this. So the question boils down to what is better – e4c5 Oct 11 '16 at 23:51
  • No, I'm not asking which is better, I'm asking if the latter is even possible. –  Oct 11 '16 at 23:53
  • 1
    I noticed that you're splitting fields with the `:` character. Don't do that, it's an RFC 1738 reserved character and has special meaning in a URL. Use a `,` or `|` instead. – wim Oct 12 '16 at 16:54

2 Answers2

2

You can start by looking at the field names:

qs.model._meta.get_all_field_names()

You are also probably going to want to work with the extensions such as field__icontains, field__gte etc. So more work will be required.

Disclaimer: try/except is the far superior way. I don't know why you want to dismiss this method.

wim
  • 338,267
  • 99
  • 616
  • 750
1

The short answer is no, but there are other options.

Django does not provide, nor make it easy to create, the kind of validation function you're asking about. There are not just fields and forward relationships that you can filter on, but also reverse relationships, for which a related_name or a related_query_name on a field in a completely different model might be the valid way to filter your querysets. And there are various filtering mechanisms, like iexact, startswith, regex, etc., that are valid as postfixes to those relationship names. So, to validate everything correctly, you would need to replicate a lot of Django's internal parsing code and that would be a big mistake.

If you just want to filter by this model's fields and forward relationships, you can use hasattr(SomeModel, my_str), but that won't always work correctly (your model has other attributes besides fields, such as methods and properties).

Instead of doing a blanket except: pass, you can at least catch the specific error that will be thrown when an invalid string is used in the filtering kwargs (it's TypeError). You can also return a 400 error to let the client know that their request was not valid, instead of silently continuing with the un-filtered queryset.

My preferred solution would be to outsource this kind of boilerplate, generalizable logic to a library, such as dynamic-rest.

Daniel Hawkins
  • 1,165
  • 13
  • 12