X-Git-Url: http://git.ithinksw.org/philo.git/blobdiff_plain/6dd799c2b33380332f09c3865a6185bdbbd48e93..82b08f79564159d7acbcaf255ed1ac1fb4882e64:/contrib/sobol/models.py
diff --git a/contrib/sobol/models.py b/contrib/sobol/models.py
index cd9b698..ee8187d 100644
--- a/contrib/sobol/models.py
+++ b/contrib/sobol/models.py
@@ -1,8 +1,10 @@
from django.conf.urls.defaults import patterns, url
from django.contrib import messages
+from django.core.exceptions import ValidationError
from django.db import models
-from django.http import HttpResponseRedirect, Http404
+from django.http import HttpResponseRedirect, Http404, HttpResponse
from django.utils import simplejson as json
+from django.utils.datastructures import SortedDict
from philo.contrib.sobol import registry
from philo.contrib.sobol.forms import SearchForm
from philo.contrib.sobol.utils import HASH_REDIRECT_GET_KEY, URL_REDIRECT_GET_KEY, SEARCH_ARG_GET_KEY, check_redirect_hash
@@ -23,51 +25,50 @@ class Search(models.Model):
def __unicode__(self):
return self.string
- def get_favored_results(self, error=5):
- """Calculate the set of most-favored results. A higher error
- will cause this method to be more reticent about adding new
- items."""
- results = self.result_urls.values_list('pk', 'url',)
-
- result_dict = {}
- for pk, url in results:
- result_dict[pk] = {'url': url, 'value': 0}
-
- clicks = Click.objects.filter(result__pk__in=result_dict.keys()).values_list('result__pk', 'datetime')
-
- now = datetime.datetime.now()
-
- def datetime_value(dt):
- days = (now - dt).days
- if days < 0:
- raise ValueError("Click dates must be in the past.")
- if days == 0:
- value = 1.0
- else:
- value = 1.0/days**2
- return value
-
- for pk, dt in clicks:
- value = datetime_value(dt)
- result_dict[pk]['value'] += value
-
- #TODO: is there a reasonable minimum value for consideration?
- subsets = {}
- for d in result_dict.values():
- subsets.setdefault(d['value'], []).append(d)
-
- # Now calculate the result set.
- results = []
+ def get_weighted_results(self, threshhold=None):
+ "Returns this search's results ordered by decreasing weight."
+ if not hasattr(self, '_weighted_results'):
+ result_qs = self.result_urls.all()
+
+ if threshhold is not None:
+ result_qs = result_qs.filter(counts__datetime__gte=threshhold)
+
+ results = [result for result in result_qs]
+
+ results.sort(cmp=lambda x,y: cmp(y.weight, x.weight))
+
+ self._weighted_results = results
- def cost(value):
- return error*sum([(value - item['value'])**2 for item in results])
+ return self._weighted_results
+
+ def get_favored_results(self, error=5, threshhold=None):
+ """
+ Calculate the set of most-favored results. A higher error
+ will cause this method to be more reticent about adding new
+ items.
- for value, subset in sorted(subsets.items(), cmp=lambda x,y: cmp(y[0], x[0])):
- if value > cost(value):
- results += subset
- else:
- break
- return results
+ The thought is to see whether there are any results which
+ vastly outstrip the other options. As such, evenly-weighted
+ results should be grouped together and either added or
+ excluded as a group.
+ """
+ if not hasattr(self, '_favored_results'):
+ results = self.get_weighted_results(threshhold)
+
+ grouped_results = SortedDict()
+
+ for result in results:
+ grouped_results.setdefault(result.weight, []).append(result)
+
+ self._favored_results = []
+
+ for value, subresults in grouped_results.items():
+ cost = error * sum([(value - result.weight)**2 for result in self._favored_results])
+ if value > cost:
+ self._favored_results += subresults
+ else:
+ break
+ return self._favored_results
class Meta:
ordering = ['string']
@@ -81,6 +82,18 @@ class ResultURL(models.Model):
def __unicode__(self):
return self.url
+ def get_weight(self, threshhold=None):
+ if not hasattr(self, '_weight'):
+ clicks = self.clicks.all()
+
+ if threshhold is not None:
+ clicks = clicks.filter(datetime__gte=threshhold)
+
+ self._weight = sum([click.weight for click in clicks])
+
+ return self._weight
+ weight = property(get_weight)
+
class Meta:
ordering = ['url']
@@ -92,6 +105,23 @@ class Click(models.Model):
def __unicode__(self):
return self.datetime.strftime('%B %d, %Y %H:%M:%S')
+ def get_weight(self, default=1, weighted=lambda value, days: value/days**2):
+ if not hasattr(self, '_weight'):
+ days = (datetime.datetime.now() - self.datetime).days
+ if days < 0:
+ raise ValueError("Click dates must be in the past.")
+ default = float(default)
+ if days == 0:
+ self._weight = float(default)
+ else:
+ self._weight = weighted(default, days)
+ return self._weight
+ weight = property(get_weight)
+
+ def clean(self):
+ if self.datetime > datetime.datetime.now():
+ raise ValidationError("Click dates must be in the past.")
+
class Meta:
ordering = ['datetime']
get_latest_by = 'datetime'
@@ -100,11 +130,13 @@ class Click(models.Model):
class SearchView(MultiView):
results_page = models.ForeignKey(Page, related_name='search_results_related')
searches = SlugMultipleChoiceField(choices=registry.iterchoices())
- enable_ajax_api = models.BooleanField("Enable AJAX API", default=True)
+ enable_ajax_api = models.BooleanField("Enable AJAX API", default=True, help_text="Search results will be available only by AJAX, not as template variables.")
placeholder_text = models.CharField(max_length=75, default="Search")
+ search_form = SearchForm
+
def __unicode__(self):
- return u"%s (%s)" % (self.placeholder_text, u", ".join([display for slug, display in registry.iterchoices()]))
+ return u"%s (%s)" % (self.placeholder_text, u", ".join([display for slug, display in registry.iterchoices() if slug in self.searches]))
def get_reverse_params(self, obj):
raise ViewCanNotProvideSubpath
@@ -116,7 +148,7 @@ class SearchView(MultiView):
)
if self.enable_ajax_api:
urlpatterns += patterns('',
- url(r'^(?P[\w-]+)', self.ajax_api_view, name='ajax_api_view')
+ url(r'^(?P[\w-]+)$', self.ajax_api_view, name='ajax_api_view')
)
return urlpatterns
@@ -130,7 +162,7 @@ class SearchView(MultiView):
context.update(extra_context or {})
if SEARCH_ARG_GET_KEY in request.GET:
- form = SearchForm(request.GET)
+ form = self.search_form(request.GET)
if form.is_valid():
search_string = request.GET[SEARCH_ARG_GET_KEY].lower()
@@ -164,6 +196,10 @@ class SearchView(MultiView):
context.update({
'searches': search_instances
})
+ else:
+ context.update({
+ 'searches': [{'verbose_name': verbose_name, 'slug': slug, 'url': self.reverse('ajax_api_view', kwargs={'slug': slug}, node=request.node), 'result_template': registry[slug].result_template} for slug, verbose_name in registry.iterchoices() if slug in self.searches]
+ })
else:
form = SearchForm()
@@ -182,8 +218,7 @@ class SearchView(MultiView):
raise Http404
search_instance = self.get_search_instance(slug, search_string)
- response = json.dumps({
- 'results': search_instance.results,
- 'template': search_instance.get_template()
- })
+ response = HttpResponse(json.dumps({
+ 'results': [result.get_context() for result in search_instance.results],
+ }))
return response
\ No newline at end of file