Search fields in multiple models in Django

2019-05-31 01:31发布

问题:

Suppose,

A model named Education contains the fields degree and field, and other model Resume contains the fields skill and role.

A third model is Candidates and has a foreign key relation with the above models.

I want the user to search candidates by their skill, role, degree or field.

For example: if a query string like {'java','developer','MS','IT'} is passed, Django should show the all the candidates matching any one of the values in the query string.

回答1:

If you're doing this with the Django Rest Framework (DRF), you will want to use django_filters as referenced by DRF.

To do what you're talking about in my project, I created a generic extension of a django_filters.Filter:

import operator
from django.db.models import Q
import django_filters

class MultiFieldFilter(django_filters.Filter):
    def __init__(self,names,*args,**kwargs):
        if len(args) == 0:
            kwargs['name'] = names[0]
        self.token_prefix = kwargs.pop('token_prefix','')
        self.token_suffix = kwargs.pop('token_suffix','')
        self.token_reducer = kwargs.pop('token_reducer',operator.and_)
        self.names = names
        django_filters.Filter.__init__(self,*args,**kwargs)

    def filter(self,qs,value):
        if value not in (None,''):
            tokens = value.split(',')
            return qs.filter(
                reduce(
                    self.token_reducer,
                    [
                        reduce(
                            operator.or_,
                            [Q(**{
                                '%s__icontains'%name:
                                    (self.token_prefix+token+self.token_suffix)})
                                        for name in self.names])
                        for token in tokens]))
        return qs

This is used in a django_filter.FilterSet like this:

class SampleSetFilter(django_filters.FilterSet):
    multi_field_search = MultiFieldFilter(names=["field_foo", "bar", "baz"],lookup_type='in')
    class Meta:
        model = SampleSet
        fields = ('multi_field_srch',)

Which is instantiated like:

 class SampleSetViewSet(viewsets.ModelViewSet):
    queryset = SampleSet.objects.all()
    serializer_class = SampleSetSerializer
    filter_class = SampleSetFilterSet # <- and vvvvvvvvvvvvvvvvvvvvvvvvvvvv
    filter_backends = (filters.OrderingFilter, filters.DjangoFilterBackend,)

Finally, a GET request like:

http://www.example.com/rest/SampleSet/?multi_field_srch=foo,de,fa,fa

will return all SampleSet's that have all of foo, de, and fa in at least one of the fields field_foo, bar, or baz.

If you specify parameter token_reducer to be operator.or_, then that query would return all SampleSets that have any of foo, de, or fa in at least one of the fields field_foo, bar, or baz.

Finally, token_prefix and token_suffix are a way to insert wild cards (substring matching) or other prefixes or suffixes.



回答2:

I don't think there's an automatic way to do this in django. But you can always OR multiple searches together using Q. The basic usage of Q is as follows:

from django.db.models import Q
Education.objects.filter(
    Q(degree__icontains=query) | Q(field__icontains=query)

To use multiple queries you can easily build together these statements using a for statement (assuming queries is a list or set of query strings):

q = Q()
for query in queries
    q = q | Q(degree__icontains=query) | Q(field__icontains=query)
Education.objects.filter(q)

Now you'd want to search over multiple models, so you would have to include those joins as well. It's not exactly clear from your question how you'd want to search, but I'd guess you'd like to basically search for candidates and that all keywords need to be matched by the found items. So the query could be done like this:

q = Q() 
for query in queries
    q = (q & (Q(education__degree__icontains=query) | 
              Q(education__field__icontains=query)  |
              Q(resume__skill__icontains=query) |
              Q(resume__role__icontains=query)
              Q(skill__icontains=query) |
              Q(role__icontains=query) |
              Q(degree__icontains=query) |
              Q(field__icontains=query)))
return Candidate.objects.filter(q)


回答3:

I'm using Django Rest Multiple Models to search on multiple models in Django Rest Framework. Make sure to read the docs carefully, especially the section on using viewsets which explains how to set up your endpoints. It seems really well built and documented, and to support everything I expect such as limits and filters.