from django.utils.text import capfirst
from django.template import loader, Context, Template, TemplateDoesNotExist
-from philo.contrib.sobol.utils import make_tracking_querydict, RegistryIterator
+from philo.contrib.sobol.utils import make_tracking_querydict
+from philo.utils.registry import Registry
if getattr(settings, 'SOBOL_USE_EVENTLET', False):
__all__ = (
- 'Result', 'BaseSearch', 'DatabaseSearch', 'URLSearch', 'JSONSearch', 'GoogleSearch', 'SearchRegistry', 'registry', 'get_search_instance'
+ 'Result', 'BaseSearch', 'DatabaseSearch', 'URLSearch', 'JSONSearch', 'GoogleSearch', 'registry', 'get_search_instance'
)
USE_CACHE = getattr(settings, 'SOBOL_USE_CACHE', True)
-class RegistrationError(Exception):
- """Raised if there is a problem registering a search with a :class:`SearchRegistry`"""
- pass
-
-
-class SearchRegistry(object):
- """Holds a registry of search types by slug."""
-
- def __init__(self):
- self._registry = {}
-
- def register(self, search, slug=None):
- """
- Register a search with the registry.
-
- :param search: The search class to register - generally a subclass of :class:`BaseSearch`
- :param slug: The slug which will be used to register the search class. If ``slug`` is ``None``, the search's default slug will be used.
- :raises: :class:`RegistrationError` if a different search is already registered with ``slug``.
-
- """
- slug = slug or search.slug
- if slug in self._registry:
- registered = self._registry[slug]
- if registered.__module__ != search.__module__:
- raise RegistrationError("A different search is already registered as `%s`" % slug)
- else:
- self._registry[slug] = search
-
- def unregister(self, search, slug=None):
- """
- Unregister a search from the registry.
-
- :param search: The search class to unregister - generally a subclass of :class:`BaseSearch`
- :param slug: If provided, the search will only be removed if it was registered with ``slug``. If not provided, the search class will be unregistered no matter what slug it was registered with.
- :raises: :class:`RegistrationError` if a slug is provided but the search registered with that slug is not ``search``.
-
- """
- if slug is not None:
- if slug in self._registry and self._registry[slug] == search:
- del self._registry[slug]
- raise RegistrationError("`%s` is not registered as `%s`" % (search, slug))
- else:
- for slug, search in self._registry.items():
- if search == search:
- del self._registry[slug]
-
- def items(self):
- """Returns a list of (slug, search) items in the registry."""
- return self._registry.items()
-
- def iteritems(self):
- """Returns an iterator over the (slug, search) pairs in the registry."""
- return RegistryIterator(self._registry, 'iteritems')
-
- def iterchoices(self):
- """Returns an iterator over (slug, search.verbose_name) pairs for the registry."""
- return RegistryIterator(self._registry, 'iteritems', lambda x: (x[0], x[1].verbose_name))
-
- def __getitem__(self, key):
- """Returns the search registered with ``key``."""
- return self._registry[key]
-
- def __iter__(self):
- """Returns an iterator over the keys in the registry."""
- return self._registry.__iter__()
-
-
-registry = SearchRegistry()
+#: A registry for :class:`BaseSearch` subclasses that should be available in the admin.
+registry = Registry()
def _make_cache_key(search, search_arg):
instance = search(search_arg)
instance.slug = slug
return instance
-
class Result(object):
--- /dev/null
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Changing field 'Node.view_object_id'
+ db.alter_column('philo_node', 'view_object_id', self.gf('django.db.models.fields.PositiveIntegerField')(null=True))
+
+ # Changing field 'Node.view_content_type'
+ db.alter_column('philo_node', 'view_content_type_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, to=orm['contenttypes.ContentType']))
+
+
+ def backwards(self, orm):
+
+ # User chose to not deal with backwards NULL issues for 'Node.view_object_id'
+ raise RuntimeError("Cannot reverse this migration. 'Node.view_object_id' and its values cannot be restored.")
+
+ # User chose to not deal with backwards NULL issues for 'Node.view_content_type'
+ raise RuntimeError("Cannot reverse this migration. 'Node.view_content_type' and its values cannot be restored.")
+
+
+ models = {
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'philo.attribute': {
+ 'Meta': {'unique_together': "(('key', 'entity_content_type', 'entity_object_id'), ('value_content_type', 'value_object_id'))", 'object_name': 'Attribute'},
+ 'entity_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attribute_entity_set'", 'to': "orm['contenttypes.ContentType']"}),
+ 'entity_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'value_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attribute_value_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+ 'value_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
+ },
+ 'philo.collection': {
+ 'Meta': {'object_name': 'Collection'},
+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'philo.collectionmember': {
+ 'Meta': {'object_name': 'CollectionMember'},
+ 'collection': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'members'", 'to': "orm['philo.Collection']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'index': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'member_content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'member_object_id': ('django.db.models.fields.PositiveIntegerField', [], {})
+ },
+ 'philo.contentlet': {
+ 'Meta': {'object_name': 'Contentlet'},
+ 'content': ('philo.models.fields.TemplateField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'contentlets'", 'to': "orm['philo.Page']"})
+ },
+ 'philo.contentreference': {
+ 'Meta': {'object_name': 'ContentReference'},
+ 'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'contentreferences'", 'to': "orm['philo.Page']"})
+ },
+ 'philo.file': {
+ 'Meta': {'object_name': 'File'},
+ 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'philo.foreignkeyvalue': {
+ 'Meta': {'object_name': 'ForeignKeyValue'},
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
+ },
+ 'philo.jsonvalue': {
+ 'Meta': {'object_name': 'JSONValue'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'value': ('philo.models.fields.JSONField', [], {'default': "'null'", 'db_index': 'True'})
+ },
+ 'philo.manytomanyvalue': {
+ 'Meta': {'object_name': 'ManyToManyValue'},
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'values': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['philo.ForeignKeyValue']", 'null': 'True', 'blank': 'True'})
+ },
+ 'philo.node': {
+ 'Meta': {'object_name': 'Node'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['philo.Node']"}),
+ 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'view_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'node_view_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+ 'view_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'philo.page': {
+ 'Meta': {'object_name': 'Page'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'pages'", 'to': "orm['philo.Template']"}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'philo.redirect': {
+ 'Meta': {'object_name': 'Redirect'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'reversing_parameters': ('philo.models.fields.JSONField', [], {'blank': 'True'}),
+ 'status_code': ('django.db.models.fields.IntegerField', [], {'default': '302'}),
+ 'target_node': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'philo_redirect_related'", 'null': 'True', 'to': "orm['philo.Node']"}),
+ 'url_or_subpath': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'})
+ },
+ 'philo.tag': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Tag'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'})
+ },
+ 'philo.template': {
+ 'Meta': {'object_name': 'Template'},
+ 'code': ('philo.models.fields.TemplateField', [], {}),
+ 'documentation': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'mimetype': ('django.db.models.fields.CharField', [], {'default': "'text/html'", 'max_length': '255'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['philo.Template']"}),
+ 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
+ }
+ }
+
+ complete_apps = ['philo']
--- /dev/null
+from django.core.validators import slug_re
+from django.template.defaultfilters import slugify
+from django.utils.encoding import smart_str
+
+
+class RegistryIterator(object):
+ """
+ Wraps the iterator returned by calling ``getattr(registry, iterattr)`` to provide late instantiation of the wrapped iterator and to allow copying of the iterator for even later instantiation.
+
+ :param registry: The object which provides the iterator at ``iterattr``.
+ :param iterattr: The name of the method on ``registry`` that provides the iterator.
+ :param transform: A function which will be called on each result from the wrapped iterator before it is returned.
+
+ """
+ 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):
+ """Returns a fresh copy of this iterator."""
+ return self.__class__(self.registry, self.iterattr, self.transform)
+
+
+class RegistrationError(Exception):
+ """Raised if there is a problem registering a object with a :class:`Registry`"""
+ pass
+
+
+class Registry(object):
+ """Holds a registry of arbitrary objects by slug."""
+
+ def __init__(self):
+ self._registry = {}
+
+ def register(self, obj, slug=None, verbose_name=None):
+ """
+ Register an object with the registry.
+
+ :param obj: The object to register.
+ :param slug: The slug which will be used to register the object. If ``slug`` is ``None``, it will be generated from ``verbose_name`` or looked for at ``obj.slug``.
+ :param verbose_name: The verbose name for the object. If ``verbose_name`` is ``None``, it will be looked for at ``obj.verbose_name``.
+ :raises: :class:`RegistrationError` if a different object is already registered with ``slug``, or if ``slug`` is not a valid slug.
+
+ """
+ verbose_name = verbose_name if verbose_name is not None else obj.verbose_name
+
+ if slug is None:
+ slug = getattr(obj, 'slug', slugify(verbose_name))
+ slug = smart_str(slug)
+
+ if not slug_re.search(slug):
+ raise RegistrationError(u"%s is not a valid slug." % slug)
+
+
+ if slug in self._registry:
+ reg = self._registry[slug]
+ if reg['obj'] != obj:
+ raise RegistrationError(u"A different object is already registered as `%s`" % slug)
+ else:
+ self._registry[slug] = {
+ 'obj': obj,
+ 'verbose_name': verbose_name
+ }
+
+ def unregister(self, obj, slug=None):
+ """
+ Unregister an object from the registry.
+
+ :param obj: The object to unregister.
+ :param slug: If provided, the object will only be removed if it was registered with ``slug``. If not provided, the object will be unregistered no matter what slug it was registered with.
+ :raises: :class:`RegistrationError` if ``slug`` is provided and an object other than ``obj`` is registered as ``slug``.
+
+ """
+ if slug is not None:
+ if slug in self._registry:
+ if self._registry[slug]['obj'] == obj:
+ del self._registry[slug]
+ else:
+ raise RegistrationError(u"`%s` is not registered as `%s`" % (obj, slug))
+ else:
+ for slug, reg in self.items():
+ if obj == reg:
+ del self._registry[slug]
+
+ def items(self):
+ """Returns a list of (slug, obj) items in the registry."""
+ return [(slug, self[slug]) for slug in self._registry]
+
+ def values(self):
+ """Returns a list of objects in the registry."""
+ return [self[slug] for slug in self._registry]
+
+ def iteritems(self):
+ """Returns a :class:`RegistryIterator` over the (slug, obj) pairs in the registry."""
+ return RegistryIterator(self._registry, 'iteritems', lambda x: (x[0], x[1]['obj']))
+
+ def itervalues(self):
+ """Returns a :class:`RegistryIterator` over the objects in the registry."""
+ return RegistryIterator(self._registry, 'itervalues', lambda x: x['obj'])
+
+ def iterchoices(self):
+ """Returns a :class:`RegistryIterator` over (slug, verbose_name) pairs for the registry."""
+ return RegistryIterator(self._registry, 'iteritems', lambda x: (x[0], x[1]['verbose_name']))
+ choices = property(iterchoices)
+
+ def get(self, key, default=None):
+ """Returns the object registered with ``key`` or ``default`` if no object was registered."""
+ try:
+ return self[key]
+ except KeyError:
+ return default
+
+ def get_slug(self, obj, default=None):
+ """Returns the slug used to register ``obj`` or ``default`` if ``obj`` was not registered."""
+ for slug, reg in self.iteritems():
+ if obj == reg:
+ return slug
+ return default
+
+ def __getitem__(self, key):
+ """Returns the obj registered with ``key``."""
+ return self._registry[key]['obj']
+
+ def __iter__(self):
+ """Returns an iterator over the keys in the registry."""
+ return self._registry.__iter__()
+
+ def __contains__(self, item):
+ return self._registry.__contains__(item)
\ No newline at end of file