docs: def search(self, query, search_fields=None):
          """
          Build a queryset matching words in the given search query,
          treating quoted terms as exact phrases and taking into
          account + and - symbols as modifiers controlling which terms
          to require and exclude.
          """
  
          # ### DETERMINE FIELDS TO SEARCH ###
  
          # Use search_fields arg if given, otherwise use search_fields
          # initially configured by the manager class.
          if search_fields:
              self._search_fields = search_fields_to_dict(search_fields)
          if not self._search_fields:
              return self.none()
  
          # ### BUILD LIST OF TERMS TO SEARCH FOR ###
  
          # Remove extra spaces, put modifiers inside quoted terms.
          terms = " ".join(query.split()).replace("+ ", "+")     \
                                         .replace('+"', '"+')    \
                                         .replace("- ", "-")     \
                                         .replace('-"', '"-')    \
                                         .split('"')
          # Strip punctuation other than modifiers from terms and create
          # terms list, first from quoted terms and then remaining words.
          terms = [("" if t[0:1] not in "+-" else t[0:1]) + t.strip(punctuation)
              for t in terms[1::2] + "".join(terms[::2]).split()]
          # Remove stop words from terms that aren't quoted or use
          # modifiers, since words with these are an explicit part of
          # the search query. If doing so ends up with an empty term
          # list, then keep the stop words.
          terms_no_stopwords = [t for t in terms if t.lower() not in
              settings.STOP_WORDS]
          get_positive_terms = lambda terms: [t.lower().strip(punctuation)
              for t in terms if t[0:1] != "-"]
          positive_terms = get_positive_terms(terms_no_stopwords)
          if positive_terms:
              terms = terms_no_stopwords
          else:
              positive_terms = get_positive_terms(terms)
          # Append positive terms (those without the negative modifier)
          # to the internal list for sorting when results are iterated.
          if not positive_terms:
              return self.none()
          else:
              self._search_terms.update(positive_terms)
  
          # ### BUILD QUERYSET FILTER ###
  
          # Create the queryset combining each set of terms.
          excluded = [reduce(iand, [~Q(**{"%s__icontains" % f: t[1:]}) for f in
              self._search_fields.keys()]) for t in terms if t[0:1] == "-"]
          required = [reduce(ior, [Q(**{"%s__icontains" % f: t[1:]}) for f in
              self._search_fields.keys()]) for t in terms if t[0:1] == "+"]
          optional = [reduce(ior, [Q(**{"%s__icontains" % f: t}) for f in
              self._search_fields.keys()]) for t in terms if t[0:1] not in "+-"]
          queryset = self
          if excluded:
              queryset = queryset.filter(reduce(iand, excluded))
          if required:
              queryset = queryset.filter(reduce(iand, required))
          # Optional terms aren't relevant to the filter if there are
          # terms that are explicitly required.
          elif optional:
              queryset = queryset.filter(reduce(ior, optional))
          return queryset.distinct()