From 700615d15881697c10a77ca65c84a84a5dd1e4d6 Mon Sep 17 00:00:00 2001 From: Stephen Burrows Date: Thu, 19 May 2011 15:18:29 -0400 Subject: [PATCH] Implemented more robust delayed registry iteration. Modules declaring new searches can now be imported after sobol without issue. --- philo/contrib/sobol/models.py | 25 +++++++++++++++++++++++-- philo/contrib/sobol/search.py | 16 ++++++++-------- philo/contrib/sobol/utils.py | 23 ++++++++++++++++++++++- 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/philo/contrib/sobol/models.py b/philo/contrib/sobol/models.py index 37bb29e..cd773a5 100644 --- a/philo/contrib/sobol/models.py +++ b/philo/contrib/sobol/models.py @@ -1,4 +1,5 @@ import datetime +import itertools from django.conf import settings from django.conf.urls.defaults import patterns, url @@ -12,7 +13,7 @@ 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 +from philo.contrib.sobol.utils import HASH_REDIRECT_GET_KEY, URL_REDIRECT_GET_KEY, SEARCH_ARG_GET_KEY, check_redirect_hash, RegistryIterator from philo.exceptions import ViewCanNotProvideSubpath from philo.models import MultiView, Page from philo.models.fields import SlugMultipleChoiceField @@ -133,9 +134,29 @@ class Click(models.Model): get_latest_by = 'datetime' +class RegistryChoiceField(SlugMultipleChoiceField): + def _get_choices(self): + if isinstance(self._choices, RegistryIterator): + return self._choices.copy() + elif hasattr(self._choices, 'next'): + choices, self._choices = itertools.tee(self._choices) + return choices + else: + return self._choices + choices = property(_get_choices) + + +try: + from south.modelsinspector import add_introspection_rules +except ImportError: + pass +else: + add_introspection_rules([], ["^philo\.contrib\.shipherd\.models\.RegistryChoiceField"]) + + class SearchView(MultiView): results_page = models.ForeignKey(Page, related_name='search_results_related') - searches = SlugMultipleChoiceField(choices=registry.iterchoices()) + searches = RegistryChoiceField(choices=registry.iterchoices()) 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") diff --git a/philo/contrib/sobol/search.py b/philo/contrib/sobol/search.py index f1a2c6b..4ab5980 100644 --- a/philo/contrib/sobol/search.py +++ b/philo/contrib/sobol/search.py @@ -11,7 +11,7 @@ from django.utils.safestring import mark_safe from django.utils.text import capfirst from django.template import loader, Context, Template -from philo.contrib.sobol.utils import make_tracking_querydict +from philo.contrib.sobol.utils import make_tracking_querydict, RegistryIterator if getattr(settings, 'SOBOL_USE_EVENTLET', False): @@ -24,7 +24,7 @@ else: __all__ = ( - 'Result', 'BaseSearch', 'DatabaseSearch', 'URLSearch', 'JSONSearch', 'GoogleSearch', 'registry' + 'Result', 'BaseSearch', 'DatabaseSearch', 'URLSearch', 'JSONSearch', 'GoogleSearch', 'SearchRegistry', 'registry' ) @@ -41,7 +41,8 @@ class RegistrationError(Exception): class SearchRegistry(object): - # Holds a registry of search types by slug. + """Holds a registry of search types by slug.""" + def __init__(self): self._registry = {} @@ -68,11 +69,10 @@ class SearchRegistry(object): return self._registry.items() def iteritems(self): - return self._registry.iteritems() + return RegistryIterator(self._registry, 'iteritems') def iterchoices(self): - for slug, search in self.iteritems(): - yield slug, search.verbose_name + return RegistryIterator(self._registry, 'iteritems', lambda x: (x[0], x[1].verbose_name)) def __getitem__(self, key): return self._registry[key] @@ -132,7 +132,7 @@ class Result(object): class BaseSearchMetaclass(type): def __new__(cls, name, bases, attrs): if 'verbose_name' not in attrs: - attrs['verbose_name'] = capfirst(convert_camelcase(name)) + attrs['verbose_name'] = capfirst(' '.join(convert_camelcase(name).rsplit(' ', 1)[:-1])) if 'slug' not in attrs: attrs['slug'] = name.lower() return super(BaseSearchMetaclass, cls).__new__(cls, name, bases, attrs) @@ -252,7 +252,7 @@ class BaseSearch(object): return make_tracking_querydict(self.search_arg, self.more_results_url) def __unicode__(self): - return ' '.join(self.__class__.verbose_name.rsplit(' ', 1)[:-1]) + ' results' + return self.verbose_name class DatabaseSearch(BaseSearch): diff --git a/philo/contrib/sobol/utils.py b/philo/contrib/sobol/utils.py index 5c52c81..50d2113 100644 --- a/philo/contrib/sobol/utils.py +++ b/philo/contrib/sobol/utils.py @@ -30,4 +30,25 @@ def make_tracking_querydict(search_arg, url): SEARCH_ARG_GET_KEY, urlquote_plus(search_arg), URL_REDIRECT_GET_KEY, urlquote(url), HASH_REDIRECT_GET_KEY, make_redirect_hash(search_arg, url)) - ) \ No newline at end of file + ) + + +class RegistryIterator(object): + def __init__(self, registry, iterattr='__iter__', transform=lambda x:x): + if not hasattr(registry, iterattr): + raise AttributeError("Registry has no attribute %s" % iterattr) + self.registry = registry + self.iterattr = iterattr + self.transform = transform + + def __iter__(self): + return self + + def next(self): + if not hasattr(self, '_iter'): + self._iter = getattr(self.registry, self.iterattr)() + + return self.transform(self._iter.next()) + + def copy(self): + return self.__class__(self.registry, self.iterattr, self.transform) \ No newline at end of file -- 2.20.1