简体   繁体   中英

Search in multiple models in Django

I have many different models in Django and I want to search for a keyword in all of them. For example, if you searched "blah", I want to show all of the products with "blah", all of the invoices with "blah", and finally all of the other models with "blah".

I can develop a view and search in all of the models separately, but it's not a good idea.

What is the best practice for this situation?

I've run into this situation a few times and one solution is to use model managers, and create distinct search methods for single and multi-word queries. Take the following example models below: Each has its own custom Model Manager, with two separate query methods. search will query single-word searches against all fields, while search_and will query each word in a list of search words, using the reduce function. Both use Q objects to accomplish multi-field lookups.

from functools import reduce
from django.db.models import Q

class ProductQuerySet(models.QuerySet):
    
    def search(self, query=None):
        qs = self
        if query is not None:
            or_lookup = (Q(product_name__icontains=query) | 
                         Q(description__icontains=query) | 
                         Q(category__icontains=query))

            qs = qs.filter(or_lookup, active=True, released=True).distinct()
        return qs

    def search_and(self, query=None):
        qs = self
        if query is not None:
            or_lookup = reduce(lambda x, y: x & y, [(Q(product_name__icontains=word) |
                                                     Q(description__icontains=word) | 
                                                     Q(category__icontains=word)) for word in query])
            qs = qs.filter(or_lookup, active=True, released=True).distinct()
        
        return qs
      

class ProductManager(models.Manager):
    def get_queryset(self):
        return ProductQuerySet(self.model, using=self._db)

    def search(self, query=None):
        return self.get_queryset().search(query=query)

    def search_and(self, query=None):
        return self.get_queryset().search_and(query=query)


class Product(models.Model):
    product_name        = models.CharField(max_length=200)
    description         = models.CharField(max_length=240, blank=True, null=True)    
    category            = models.CharField(max_length=100, choices=CATEGORY)
   
    objects = ProductManager()

    def __str__(self):
        return self.product_name


class ProfileQuerySet(models.QuerySet):
    
    def search(self, query=None):
        qs = self
        if query is not None:
            or_lookup = (Q(full_name__icontains=query) | 
                         Q(job_title__icontains=query) | 
                         Q(function__icontains=query))

            qs = qs.filter(or_lookup, active=True, released=True).distinct()
        return qs

    def search_and(self, query=None):
        qs = self
        if query is not None:
            or_lookup = reduce(lambda x, y: x & y, [(Q(full_name__icontains=word) |
                                                     Q(job_title__icontains=word) | 
                                                     Q(function__icontains=word)) for word in query])
            qs = qs.filter(or_lookup, active=True, released=True).distinct()
        
        return qs
      

class ProfileManager(models.Manager):
    def get_queryset(self):
        return ProfileQuerySet(self.model, using=self._db)

    def search(self, query=None):
        return self.get_queryset().search(query=query)

    def search_and(self, query=None):
        return self.get_queryset().search_and(query=query)


class Profile(models.Model):
    full_name           = models.CharField(max_length=200)
    job_title           = models.CharField(max_length=240, blank=True, null=True)    
    function            = models.CharField(max_length=100, choices=FUNCTION)
   
    objects = ProfileManager()

    def __str__(self):
        return self.full_name

Now to use these in a search view, which you can point at as many model managers as you like, which can query as many fields as you like. Using the example models above, here's a sample view, below. It passes the search term or terms to the appropriate model manager method, based on the count of terms (either 1 or more than 1).



def application_search(request):
    data = dict()
    
    if 'query' in request.GET: 
        query_list = request.GET.get("query", None).split()
        if query_list:
            try:
                if len(query_list) > 1:
                    products = Product.objects.search_and(query=query_list)
                    profiles = Profile.objects.search_and(query=query_list)
                else:
                    products = Product.objects.search(query=query_list[0])
                    profiles = Profile.objects.search(query=query_list[0])
            except:
                # Throw exception or log error here 
            try:
                queryset_chain = chain(products, profiles) # combines querysets into one
                results = sorted(queryset_chain, key=lambda instance: instance.id, reverse=True) #sorts results by ID
            except:
                results = None        

        data['results'] = render_to_string('pages/my_search_result_page.html', {'results': results})

        return JsonResponse(data)

The query in this view is actually being passed to the backend via AJAX, but you may do it differently based on your needs and template design.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM