1 from django.conf.urls.defaults import patterns, url
2 from django.contrib import messages
3 from django.db import models
4 from django.http import HttpResponseRedirect, Http404
5 from django.utils import simplejson as json
6 from philo.contrib.sobol import registry
7 from philo.contrib.sobol.forms import SearchForm
8 from philo.contrib.sobol.utils import HASH_REDIRECT_GET_KEY, URL_REDIRECT_GET_KEY, SEARCH_ARG_GET_KEY, check_redirect_hash
9 from philo.exceptions import ViewCanNotProvideSubpath
10 from philo.models import MultiView, Page, SlugMultipleChoiceField
11 from philo.validators import RedirectValidator
19 class Search(models.Model):
20 string = models.TextField()
22 def __unicode__(self):
23 return self.search_string
27 verbose_name_plural = 'searches'
30 class ResultURL(models.Model):
31 search = models.ForeignKey(Search, related_name='result_urls')
32 url = models.TextField(validators=[RedirectValidator()])
34 def __unicode__(self):
41 class Click(models.Model):
42 result = models.ForeignKey(ResultURL, related_name='clicks')
43 datetime = models.DateTimeField()
45 def __unicode__(self):
46 return self.datetime.strftime('%B %d, %Y %H:%M:%S')
49 ordering = ['datetime']
50 get_latest_by = 'datetime'
53 class SearchView(MultiView):
54 results_page = models.ForeignKey(Page, related_name='search_results_related')
55 searches = SlugMultipleChoiceField(choices=registry.iterchoices())
56 allow_partial_loading = models.BooleanField(default=True)
57 placeholder_text = models.CharField(max_length=75, default="Search")
59 def get_reverse_params(self, obj):
60 raise ViewCanNotProvideSubpath
63 def urlpatterns(self):
64 urlpatterns = patterns('',
65 url(r'^$', self.results_view, name='results'),
67 if self.allow_partial_loading:
68 urlpatterns += patterns('',
69 url(r'^(?P<slug>[\w-]+)/?', self.partial_ajax_results_view, name='partial_ajax_results_view')
73 def results_view(self, request, extra_context=None):
76 context = self.get_context()
77 context.update(extra_context or {})
79 if SEARCH_ARG_GET_KEY in request.GET:
80 form = SearchForm(request.GET)
83 search_string = request.GET[SEARCH_ARG_GET_KEY].lower()
84 url = request.GET.get(URL_REDIRECT_GET_KEY)
85 hash = request.GET.get(HASH_REDIRECT_GET_KEY)
88 if check_redirect_hash(hash, search_string, url):
89 # Create the necessary models
90 search = Search.objects.get_or_create(string=search_string)[0]
91 result_url = search.result_urls.get_or_create(url=url)[0]
92 result_url.clicks.create(datetime=datetime.datetime.now())
93 return HttpResponseRedirect(url)
95 messages.add_message(request, messages.INFO, "The link you followed had been tampered with. Here are all the results for your search term instead!")
96 # TODO: Should search_string be escaped here?
97 return HttpResponseRedirect("%s?%s=%s" % (request.path, SEARCH_ARG_GET_KEY, search_string))
98 if not self.allow_partial_loading:
101 pool = eventlet.GreenPool()
102 for slug in self.searches:
103 search = registry[slug]
104 search_instance = search(search_string)
105 search_instances.append(search_instance)
107 pool.spawn_n(self.make_result_cache, search_instance)
109 self.make_result_cache(search_instance)
113 'searches': search_instances
120 return self.results_page.render_to_response(request, extra_context=context)
122 def make_result_cache(self, search_instance):
123 search_instance.results
125 def partial_ajax_results_view(self, request, slug, extra_context=None):
126 search_string = request.GET.get(SEARCH_ARG_GET_KEY)
128 if not request.is_ajax() or not self.allow_partial_loading or slug not in self.searches or search_string is None:
131 search = registry[slug]
132 search_instance = search(search_string.lower())
133 results = search_instance.results
134 response = json.dumps({
136 'template': search_instance.get_ajax_result_template()