8113750856eed46036af30ce96e35e49aec48c1e
[philo.git] / contrib / sobol / models.py
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
12 import datetime
13 try:
14         import eventlet
15 except:
16         eventlet = False
17
18
19 class Search(models.Model):
20         string = models.TextField()
21         
22         def __unicode__(self):
23                 return self.search_string
24         
25         class Meta:
26                 ordering = ['string']
27                 verbose_name_plural = 'searches'
28
29
30 class ResultURL(models.Model):
31         search = models.ForeignKey(Search, related_name='result_urls')
32         url = models.TextField(validators=[RedirectValidator()])
33         
34         def __unicode__(self):
35                 return self.url
36         
37         class Meta:
38                 ordering = ['url']
39
40
41 class Click(models.Model):
42         result = models.ForeignKey(ResultURL, related_name='clicks')
43         datetime = models.DateTimeField()
44         
45         def __unicode__(self):
46                 return self.datetime.strftime('%B %d, %Y %H:%M:%S')
47         
48         class Meta:
49                 ordering = ['datetime']
50                 get_latest_by = 'datetime'
51
52
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")
58         
59         def get_reverse_params(self, obj):
60                 raise ViewCanNotProvideSubpath
61         
62         @property
63         def urlpatterns(self):
64                 urlpatterns = patterns('',
65                         url(r'^$', self.results_view, name='results'),
66                 )
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')
70                         )
71                 return urlpatterns
72         
73         def results_view(self, request, extra_context=None):
74                 results = None
75                 
76                 context = self.get_context()
77                 context.update(extra_context or {})
78                 
79                 if SEARCH_ARG_GET_KEY in request.GET:
80                         form = SearchForm(request.GET)
81                         
82                         if form.is_valid():
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)
86                                 
87                                 if url and hash:
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)
94                                         else:
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:
99                                         search_instances = []
100                                         if eventlet:
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)
106                                                 if eventlet:
107                                                         pool.spawn_n(self.make_result_cache, search_instance)
108                                                 else:
109                                                         self.make_result_cache(search_instance)
110                                         if eventlet:
111                                                 pool.waitall()
112                                         context.update({
113                                                 'searches': search_instances
114                                         })
115                 else:
116                         form = SearchForm()
117                 context.update({
118                         'form': form
119                 })
120                 return self.results_page.render_to_response(request, extra_context=context)
121         
122         def make_result_cache(self, search_instance):
123                 search_instance.results
124         
125         def partial_ajax_results_view(self, request, slug, extra_context=None):
126                 search_string = request.GET.get(SEARCH_ARG_GET_KEY)
127                 
128                 if not request.is_ajax() or not self.allow_partial_loading or slug not in self.searches or search_string is None:
129                         raise Http404
130                 
131                 search = registry[slug]
132                 search_instance = search(search_string.lower())
133                 results = search_instance.results
134                 response = json.dumps({
135                         'results': results,
136                         'template': search_instance.get_ajax_result_template()
137                 })
138                 return response