search.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. from django.db.models import Q
  2. from django.utils.translation import ugettext_lazy as _
  3. from haystack.inputs import AutoQuery
  4. from haystack.query import SearchQuerySet, RelatedSearchQuerySet
  5. from misago.acl.exceptions import ACLError403, ACLError404
  6. from misago.models import Forum, Thread, Post, User
  7. class SearchException(Exception):
  8. def __init__(self, message=None, suggestion=None):
  9. self.message = message
  10. self.suggestion = suggestion
  11. def __unicode__(self):
  12. return self.message
  13. class SearchQuery(object):
  14. def __init__(self, raw_query=None):
  15. """
  16. Build search query object
  17. """
  18. if raw_query:
  19. self.parse_query(raw_query)
  20. def parse_query(self, raw_query):
  21. """
  22. Parse raw search query into dict of lists of words that should be found and cant be found in string
  23. """
  24. self.criteria = {'+': [], '-': []}
  25. for word in unicode(raw_query).split():
  26. # Trim word and skip it if its empty
  27. word = unicode(word).strip().lower()
  28. if len(word) == 0:
  29. pass
  30. # Find word mode
  31. mode = '+'
  32. if word[0] == '-':
  33. mode = '-'
  34. word = unicode(word[1:]).strip()
  35. # Strip extra crap
  36. word = ''.join(e for e in word if e.isalnum())
  37. # Slice word?
  38. if len(word) <= 3:
  39. raise SearchException(_("One or more search phrases are shorter than four characters."))
  40. if mode == '+':
  41. if len(word) == 5:
  42. word = word[0:-1]
  43. if len(word) == 6:
  44. word = word[0:-2]
  45. if len(word) > 6:
  46. word = word[0:-3]
  47. self.criteria[mode].append(word)
  48. # Complain that there are no positive matches
  49. if not self.criteria['+'] and not self.criteria['-']:
  50. raise SearchException(_("Search query is invalid."))
  51. def search(self, value):
  52. """
  53. See if value meets search criteria, return True for success and False otherwhise
  54. """
  55. try:
  56. value = unicode(value).strip().lower()
  57. # Search for only
  58. if self.criteria['+'] and not self.criteria['-']:
  59. return self.search_for(value)
  60. # Search against only
  61. if self.criteria['-'] and not self.criteria['+']:
  62. return self.search_against(value)
  63. # Search if contains for values but not against values
  64. return self.search_for(value) and not self.search_against(value)
  65. except AttributeError:
  66. raise SearchException(_("You have to define search query before you will be able to search."))
  67. def search_for(self, value):
  68. """
  69. See if value is required
  70. """
  71. for word in self.criteria['+']:
  72. if value.find(word) != -1:
  73. return True
  74. return False
  75. def search_against(self, value):
  76. """
  77. See if value is forbidden
  78. """
  79. for word in self.criteria['-']:
  80. if value.find(word) != -1:
  81. return True
  82. return False
  83. class MisagoSearchQuerySet(object):
  84. def __init__(self, user, acl):
  85. self._content = None
  86. self._thread_name = None
  87. self._user_name = None
  88. self._after = None
  89. self._before = None
  90. self._children = None
  91. self._threads = None
  92. self._forums = None
  93. self.user = user
  94. self.acl = acl
  95. def search_in(self, target):
  96. try:
  97. self.allow_forum_search(target)
  98. except AttributeError:
  99. self.allow_thread_search(target)
  100. def allow_forum_search(self, forum):
  101. if forum.special == 'private_threads':
  102. if not self.acl.private_threads.can_participate():
  103. raise ACLError403()
  104. if self.acl.private_threads.is_mod():
  105. self._threads = [t.pk for t in forum.thread_set.filter(Q(participants__id=self.user.pk) | Q(replies_reported__gt=0)).iterator()]
  106. else:
  107. self._threads = [t.pk for t in forum.thread_set.filter(participants__id=self.user.pk).iterator()]
  108. elif forum.special == 'reports':
  109. if not self.acl.reports.can_handle():
  110. raise ACLError403()
  111. self._forums = [forum.pk]
  112. else:
  113. self._forums = Forum.objects.readable_forums(self.acl)
  114. def allow_thread_search(self, thread):
  115. self.allow_forum_search(thread.forum)
  116. if thread.forum.special == 'private_threads':
  117. if thread.pk in self._threads:
  118. self._threads = [thread.pk]
  119. else:
  120. raise ACLError403()
  121. self._threads = [thread.pk]
  122. def search_content(self, query):
  123. self._content = query
  124. def restrict_threads(self, threads=None):
  125. self._threads = threads
  126. def search_thread_name(self, query):
  127. self._thread_name = query
  128. def search_user_name(self, query):
  129. self._user_name = query
  130. def after(self, datetime):
  131. self._after = datetime
  132. def before(self, datetime):
  133. self._before = datetime
  134. def in_forum(self, forum, children=False):
  135. self._forum = forum
  136. self._children = children
  137. @property
  138. def query(self):
  139. try:
  140. return self._searchquery
  141. except AttributeError:
  142. pass
  143. sqs = SearchQuerySet()
  144. if self._content:
  145. sqs = sqs.auto_query(self._content)
  146. if self._thread_name:
  147. sqs = sqs.filter(thread_name=AutoQuery(self._thread_name), start_post=1)
  148. if self._user_name:
  149. sqs = sqs.filter(username=self._user_name)
  150. if self._before:
  151. sqs = sqs.filter(date__lte=self._before)
  152. if self._after:
  153. sqs = sqs.filter(date__gte=self._after)
  154. if self._threads:
  155. sqs = sqs.filter(thread__in=self._threads)
  156. if self._forums:
  157. sqs = sqs.filter(forum__in=self._forums)
  158. self._searchquery = sqs
  159. return self._searchquery