Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 01c6a38
Showing
14 changed files
with
386 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
*.pyc | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
================= | ||
django-cms-search | ||
================= | ||
|
||
This package provides multilingual search indexes for easy Haystack integration with django CMS. | ||
|
||
Usage | ||
===== | ||
|
||
After installing django-cms-search through your package manager of choice, add ``cms_search`` to your | ||
``INSTALLED_APPS``. That's it. | ||
|
||
For setting up Haystack, please refer to their `documentation <http://docs.haystacksearch.org/dev/>`_. | ||
|
||
Customizing the Index | ||
--------------------- | ||
|
||
You can customize what parts of a ``CMSPlugin`` end up in the index with two class attributes on ``CMSPlugin`` | ||
subclasses: | ||
|
||
* ``search_fields``: a list of field names to index. | ||
* ``search_fulltext``: if ``True``, the index renders the plugin and adds the result (sans HTML tags) to the index. | ||
|
||
Helpers | ||
======= | ||
|
||
django-cms-search provides a couple of useful helpers to deal with multilingual content. | ||
|
||
``cms_search.helpers.indexes.MultiLanguageIndex`` | ||
------------------------------------------------- | ||
|
||
A ``SearchIndex`` that dynamically adds translated fields to the search index. An example for when this is useful is the | ||
app hook infrastructure from django CMS. When a model's ``get_absolute_url`` uses a url pattern that is attached to an | ||
app hook, the URL varies depending on the language. A usage example:: | ||
|
||
from haystack import indexes | ||
from cms_search.helpers.indexes import MultiLanguageIndex | ||
|
||
class NewsIndex(MultiLanguageIndex): | ||
text = indexes.CharField(document=True, use_template=True) | ||
title = indexes.CharField(model_attr='title') | ||
url = indexes.CharField(stored=True) | ||
|
||
def prepare_url(self, obj): | ||
return obj.get_absolute_url() | ||
|
||
class HaystackTrans: | ||
fields = ('url', 'title') | ||
|
||
A few things to note: | ||
|
||
* ``MultiLanguageIndex`` dynamically creates translated fields. The name of the dynamic fields is a concatenation of the | ||
original field name, an underscore and the language code. | ||
* If you define a ``prepare`` method for a translated field, that method will be called multiple times, with changing | ||
active language. | ||
* In the above example, you might want to catch ``NoReverseMatch`` exceptions if you don't have activated the app hook | ||
for all languages defined in ``settings.LANGUAGES``. | ||
* The ``model_attr`` attribute is handled somewhat specially. The index tries to find a field on the model called | ||
``model_attr + '_' + language_code``. If it exists, it is used as the translated value. But it isn't possible to supply | ||
the name of a model method and let the index call it with varying activated languages. Use ``prepare_myfieldname`` for | ||
that case. | ||
|
||
``{% get_translated_value %}`` template tag | ||
------------------------------------------- | ||
|
||
This template tag is most useful in combination with the ``MultiLanguageIndex``. You can use it while looping through | ||
search results, and it will automatically pick up the translated field for the current language or fall back to another | ||
available language (in the order defined in ``settings.LANGUAGES``). Example:: | ||
|
||
{% load cms_search %} | ||
|
||
<ul class="search-results"> | ||
{% for result in page.object_list %} | ||
<li><a href="{% get_translated_value result "url" %}">{% get_translated_value result "title" %}</a></li> | ||
{% endfor %} | ||
</ul> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
__author__ = 'benjamin.wohlwend' | ||
__version__ = '0.1' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
from django.conf.urls.defaults import patterns, url | ||
from django.utils.translation import ugettext_lazy as _ | ||
|
||
from cms.app_base import CMSApp | ||
from cms.apphook_pool import apphook_pool | ||
|
||
from haystack.views import search_view_factory | ||
|
||
class HaystackSearchApphook(CMSApp): | ||
name = _("search apphook") | ||
urls = [patterns('', | ||
url('^$', search_view_factory(), name='haystack-search'), | ||
),] | ||
|
||
apphook_pool.register(HaystackSearchApphook) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
__author__ = 'benjamin.wohlwend' | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
from django.conf import settings | ||
from haystack import indexes | ||
from django.utils.translation import get_language, activate | ||
|
||
class MultiLangTemplateField(indexes.CharField): | ||
|
||
def __init__(self, **kwargs): | ||
kwargs['use_template'] = True | ||
super(MultiLangTemplateField, self).__init__(**kwargs) | ||
|
||
def prepare_template(self, obj): | ||
content = [] | ||
current_lang = get_language() | ||
try: | ||
for lang, lang_name in settings.LANGUAGES: | ||
activate(lang) | ||
content.append(super(MultiLangTemplateField, self).prepare_template(obj)) | ||
finally: | ||
activate(current_lang) | ||
|
||
return '\n'.join(content) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import inspect | ||
|
||
from django.conf import settings | ||
from haystack import indexes | ||
from django.utils.translation import activate, get_language | ||
|
||
|
||
class MultiLangPrepareDecorator(object): | ||
def __init__(self, language): | ||
self.language = language | ||
|
||
def __call__(self, func): | ||
def wrapped(*args): | ||
old_language = get_language() | ||
activate(self.language) | ||
try: | ||
return func(*args) | ||
finally: | ||
activate(old_language) | ||
return wrapped | ||
|
||
|
||
class MultiLanguageIndexBase(indexes.DeclarativeMetaclass): | ||
|
||
@classmethod | ||
def _get_field_copy(cls, field, language): | ||
model_attr = field.model_attr | ||
if model_attr: | ||
model_attr += '_%s' % language.replace('-', '_') | ||
arg_names = inspect.getargspec(indexes.SearchField.__init__)[0][2:] | ||
kwargs = dict((arg_name, getattr(field, arg_name)) for arg_name in arg_names) | ||
kwargs['model_attr'] = model_attr | ||
copy = field.__class__(**kwargs) | ||
copy.null = True | ||
return copy | ||
|
||
def __new__(cls, name, bases, attrs): | ||
if 'HaystackTrans' in attrs: | ||
for field in getattr(attrs['HaystackTrans'], 'fields', []): | ||
if field not in attrs: | ||
continue | ||
for lang_tuple in settings.LANGUAGES: | ||
lang = lang_tuple[0] | ||
safe_lang = lang.replace('-', '_') | ||
attrs['%s_%s' % (field, safe_lang)] = cls._get_field_copy(attrs[field], lang) | ||
if 'prepare_' + field in attrs: | ||
attrs['prepare_%s_%s' % (field, safe_lang)] = MultiLangPrepareDecorator(lang)(attrs['prepare_' + field]) | ||
return super(MultiLanguageIndexBase, cls).__new__(cls, name, bases, attrs) | ||
|
||
|
||
class MultiLanguageIndex(indexes.SearchIndex): | ||
__metaclass__ = MultiLanguageIndexBase |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
__author__ = 'benjamin.wohlwend' | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
from classytags.arguments import Argument | ||
from classytags.core import Options | ||
from classytags.helpers import AsTag | ||
import haystack | ||
from django import template | ||
from django.conf import settings | ||
from django.utils.translation import get_language | ||
|
||
register = template.Library() | ||
|
||
class GetTransFieldTag(AsTag): | ||
""" | ||
Templatetag to access translated attributes of a `haystack.models.SearchResult`. | ||
By default, it looks for a translated field at `field_name`_`language`. To | ||
customize this, subclass `GetTransFieldTag` and override `get_translated_value`. | ||
""" | ||
EMPTY_VALUE = '' | ||
FALLBACK = True | ||
name = 'get_translated_value' | ||
options = Options( | ||
Argument('obj'), | ||
Argument('field_name'), | ||
'as', | ||
Argument('varname', resolve=False, required=False) | ||
) | ||
|
||
def get_value(self, context, obj, field_name): | ||
""" | ||
gets the translated value of field name. If `FALLBACK`evaluates to `True` and the field | ||
has no translation for the current language, it tries to find a fallback value, using | ||
the languages defined in `settings.LANGUAGES`. | ||
""" | ||
language = get_language() | ||
value = self.get_translated_value(obj, field_name, language) | ||
if value: | ||
return value | ||
if self.FALLBACK: | ||
for lang, lang_name in settings.LANGUAGES: | ||
if lang == language: | ||
# already tried this one... | ||
continue | ||
value = self.get_translated_value(obj, field_name, lang) | ||
if value: | ||
return value | ||
untranslated = getattr(obj, field_name) | ||
if self._is_truthy(untranslated): | ||
return untranslated | ||
else: | ||
return self.EMPTY_VALUE | ||
|
||
def get_translated_value(self, obj, field_name, language): | ||
value = getattr(obj, '%s_%s' % (field_name, language), '') | ||
if self._is_truthy(value): | ||
return value | ||
else: | ||
return self.EMPTY_VALUE | ||
|
||
def _is_truthy(self, value): | ||
if isinstance(value, haystack.fields.NOT_PROVIDED): | ||
return False | ||
elif isinstance(value, basestring) and value.startswith('<haystack.fields.NOT_PROVIDED instance at '): #UUUGLY!! | ||
return False | ||
return bool(value) | ||
|
||
|
||
register.tag(GetTransFieldTag) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
from cms.models import Page | ||
from cms.models.managers import PageManager | ||
from django.conf import settings | ||
from django.utils.translation import ugettext_lazy, string_concat, activate, get_language | ||
|
||
def proxy_name(language_code): | ||
safe_code = language_code.replace('-', ' ').title().replace(' ', '_') | ||
return 'Page_%s' % safe_code | ||
|
||
|
||
def page_proxy_factory(language_code, language_name): | ||
def get_absolute_url(self): | ||
if 'cms.middleware.multilingual.MultilingualURLMiddleware' in settings.MIDDLEWARE_CLASSES: | ||
old_language = get_language() | ||
try: | ||
activate(language_code) | ||
return '/%s%s' % (language_code, Page.get_absolute_url(self)) | ||
finally: | ||
activate(old_language) | ||
else: | ||
return Page.get_absolute_url(self) | ||
|
||
class Meta: | ||
proxy = True | ||
app_label = 'cms_search' | ||
if len(settings.LANGUAGES) > 1: | ||
verbose_name = string_concat(Page._meta.verbose_name, ' (', language_name, ')') | ||
verbose_name_plural = string_concat(Page._meta.verbose_name_plural, ' (', language_name, ')') | ||
else: | ||
verbose_name = Page._meta.verbose_name | ||
verbose_name_plural = Page._meta.verbose_name_plural | ||
|
||
attrs = {'__module__': Page.__module__, | ||
'Meta': Meta, | ||
'objects': PageManager(), | ||
'get_absolute_url': get_absolute_url} | ||
|
||
_PageProxy = type(proxy_name(language_code), (Page,), attrs) | ||
|
||
_PageProxy._meta.parent_attr = 'parent' | ||
_PageProxy._meta.left_attr = 'lft' | ||
_PageProxy._meta.right_attr = 'rght' | ||
_PageProxy._meta.tree_id_attr = 'tree_id' | ||
|
||
return _PageProxy | ||
|
||
module_namespace = globals() | ||
|
||
for language_code, language_name in settings.LANGUAGES: | ||
if isinstance(language_name, basestring): | ||
language_name = ugettext_lazy(language_name) | ||
proxy_model = page_proxy_factory(language_code, language_name) | ||
module_namespace[proxy_model.__name__] = proxy_model |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
from django.conf import settings | ||
from django.utils.encoding import force_unicode | ||
from django.utils.html import strip_tags | ||
|
||
from haystack import indexes, site | ||
|
||
from cms.models.pluginmodel import CMSPlugin | ||
|
||
from cms_search import models as proxy_models | ||
|
||
def page_index_factory(language_code, proxy_model): | ||
|
||
class _PageIndex(indexes.SearchIndex): | ||
language = language_code | ||
|
||
text = indexes.CharField(document=True, use_template=False) | ||
pub_date = indexes.DateTimeField(model_attr='publication_date') | ||
login_required = indexes.BooleanField(model_attr='login_required') | ||
url = indexes.CharField(stored=True, indexed=False, model_attr='get_absolute_url') | ||
title = indexes.CharField(stored=True, indexed=False, model_attr='get_title') | ||
|
||
def prepare(self, obj): | ||
self.prepared_data = super(_PageIndex, self).prepare(obj) | ||
plugins = CMSPlugin.objects.filter(language=language_code, placeholder__in=obj.placeholders.all()) | ||
text = '' | ||
for plugin in plugins: | ||
instance, _ = plugin.get_plugin_instance() | ||
if hasattr(instance, 'search_fields'): | ||
text += u''.join(force_unicode(strip_tags(getattr(instance, field, ''))) for field in instance.search_fields) | ||
if getattr(instance, 'search_fulltext', False): | ||
text += strip_tags(instance.render_plugin()) | ||
self.prepared_data['text'] = text | ||
return self.prepared_data | ||
|
||
def get_queryset(self): | ||
qs = proxy_model.objects.published().filter(title_set__language=language_code).distinct() | ||
if 'publisher' in settings.INSTALLED_APPS: | ||
qs = qs.filter(publisher_is_draft=True) | ||
return qs | ||
|
||
return _PageIndex | ||
|
||
for language_code, language_name in settings.LANGUAGES: | ||
proxy_model = getattr(proxy_models, proxy_models.proxy_name(language_code)) | ||
index = page_index_factory(language_code, proxy_model) | ||
if proxy_model: | ||
site.register(proxy_model, index) | ||
else: | ||
print "no page proxy model found for language %s" % language_code |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package_name = 'cms_search' | ||
name = 'django-cms-search' | ||
author = 'Divio GmbH' | ||
author_email = 'developers@divio.ch' | ||
description = "An extension to django CMS to provide multilingual Haystack indexes" | ||
version = __import__(package_name).__version__ | ||
project_url = 'http://github.com/divio/%s' % name | ||
license = 'BSD' |
Oops, something went wrong.