You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

forms.py 5.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. from collections import defaultdict
  2. from django import forms
  3. from django.forms.widgets import Input
  4. from django.conf import settings
  5. from django.utils.translation import ugettext_lazy as _
  6. from haystack.forms import FacetedSearchForm
  7. from oscar.core.loading import get_class
  8. is_solr_supported = get_class('search.features', 'is_solr_supported')
  9. class SearchInput(Input):
  10. """
  11. Defining a search type widget
  12. This is an HTML5 thing and works nicely with Safari, other browsers default
  13. back to using the default "text" type
  14. """
  15. input_type = 'search'
  16. # Build a dict of valid queries
  17. VALID_FACET_QUERIES = defaultdict(list)
  18. for facet in settings.OSCAR_SEARCH_FACETS['queries'].values():
  19. field_name = "%s_exact" % facet['field']
  20. queries = [t[1] for t in facet['queries']]
  21. VALID_FACET_QUERIES[field_name].extend(queries)
  22. class SearchForm(FacetedSearchForm):
  23. """
  24. In Haystack, the search form is used for interpreting
  25. and sub-filtering the SQS.
  26. """
  27. # Use a tabindex of 1 so that users can hit tab on any page and it will
  28. # focus on the search widget.
  29. q = forms.CharField(
  30. required=False, label=_('Search'),
  31. widget=SearchInput({"placeholder": _('Search'), "tabindex": "1"}))
  32. # Search
  33. RELEVANCY = "relevancy"
  34. TOP_RATED = "rating"
  35. NEWEST = "newest"
  36. PRICE_HIGH_TO_LOW = "price-desc"
  37. PRICE_LOW_TO_HIGH = "price-asc"
  38. TITLE_A_TO_Z = "title-asc"
  39. TITLE_Z_TO_A = "title-desc"
  40. SORT_BY_CHOICES = [
  41. (RELEVANCY, _("Relevancy")),
  42. (TOP_RATED, _("Customer rating")),
  43. (NEWEST, _("Newest")),
  44. (PRICE_HIGH_TO_LOW, _("Price high to low")),
  45. (PRICE_LOW_TO_HIGH, _("Price low to high")),
  46. (TITLE_A_TO_Z, _("Title A to Z")),
  47. (TITLE_Z_TO_A, _("Title Z to A")),
  48. ]
  49. # Map query params to sorting fields. Note relevancy isn't included here
  50. # as we assume results are returned in relevancy order in the absence of an
  51. # explicit sort field being passed to the search backend.
  52. SORT_BY_MAP = {
  53. TOP_RATED: '-rating',
  54. NEWEST: '-date_created',
  55. PRICE_HIGH_TO_LOW: '-price',
  56. PRICE_LOW_TO_HIGH: 'price',
  57. TITLE_A_TO_Z: 'title_s',
  58. TITLE_Z_TO_A: '-title_s',
  59. }
  60. # Non Solr backends don't support dynamic fields so we just sort on title
  61. if not is_solr_supported():
  62. SORT_BY_MAP[TITLE_A_TO_Z] = 'title'
  63. SORT_BY_MAP[TITLE_Z_TO_A] = '-title'
  64. sort_by = forms.ChoiceField( label=_("Sort by"),
  65. choices=SORT_BY_CHOICES, widget=forms.Select(), required=False)
  66. @property
  67. def selected_multi_facets(self):
  68. """
  69. Validate and return the selected facets
  70. """
  71. # Process selected facets into a dict(field->[*values]) to handle
  72. # multi-faceting
  73. selected_multi_facets = defaultdict(list)
  74. for facet_kv in self.selected_facets:
  75. if ":" not in facet_kv:
  76. continue
  77. field_name, value = facet_kv.split(':', 1)
  78. # Validate query facets as they as passed unescaped to Solr
  79. if field_name in VALID_FACET_QUERIES:
  80. if value not in VALID_FACET_QUERIES[field_name]:
  81. # Invalid query value
  82. continue
  83. selected_multi_facets[field_name].append(value)
  84. return selected_multi_facets
  85. def search(self):
  86. # We replace the 'search' method from FacetedSearchForm, so that we can
  87. # handle range queries
  88. # Note, we call super on a parent class as the default faceted view
  89. # escapes everything (which doesn't work for price range queries)
  90. sqs = super(FacetedSearchForm, self).search()
  91. # We need to process each facet to ensure that the field name and the
  92. # value are quoted correctly and separately:
  93. for field, values in self.selected_multi_facets.items():
  94. if not values:
  95. continue
  96. if field in VALID_FACET_QUERIES:
  97. # Query facet - don't wrap value in speech marks and don't
  98. # clean value. Query values should have been validated by this
  99. # point and so we don't need to escape them.
  100. sqs = sqs.narrow(u'%s:(%s)' % (
  101. field, " OR ".join(values)))
  102. else:
  103. # Field facet - clean and quote the values
  104. clean_values = [
  105. '"%s"' % sqs.query.clean(val) for val in values]
  106. sqs = sqs.narrow(u'%s:(%s)' % (
  107. field, " OR ".join(clean_values)))
  108. if self.is_valid() and 'sort_by' in self.cleaned_data:
  109. sort_field = self.SORT_BY_MAP.get(
  110. self.cleaned_data['sort_by'], None)
  111. if sort_field:
  112. sqs = sqs.order_by(sort_field)
  113. return sqs
  114. class BrowseCategoryForm(SearchForm):
  115. """
  116. Variant of SearchForm that returns all products (instead of none) if no
  117. query is specified.
  118. """
  119. def no_query_found(self):
  120. return self.searchqueryset