Merge pull request #982 from kaedroho/feature/streamfield-search

[streamfield] Search
This commit is contained in:
Karl Hobley 2015-02-19 09:12:54 +00:00
commit 22a84e1ca3
4 changed files with 127 additions and 1 deletions

View file

@ -225,6 +225,12 @@ class Block(six.with_metaclass(BaseBlock, object)):
"""
return force_text(value)
def get_searchable_content(self, value):
"""
Returns a list of strings containing text content within this block to be used in a search engine.
"""
return []
def __eq__(self, other):
"""
The deep_deconstruct method in django.db.migrations.autodetector.MigrationAutodetector does not
@ -347,17 +353,23 @@ class FieldBlock(Block):
def clean(self, value):
return self.field.clean(value)
class CharBlock(FieldBlock):
def __init__(self, required=True, help_text=None, max_length=None, min_length=None, **kwargs):
# TODO: decide what to do about 'label' and 'initial' parameters to the form field
self.field = forms.CharField(required=required, help_text=help_text, max_length=max_length, min_length=min_length)
super(CharBlock, self).__init__(**kwargs)
def get_searchable_content(self, value):
return [force_text(value)]
class URLBlock(FieldBlock):
def __init__(self, required=True, help_text=None, max_length=None, min_length=None, **kwargs):
self.field = forms.URLField(required=required, help_text=help_text, max_length=max_length, min_length=min_length)
super(URLBlock, self).__init__(**kwargs)
class RichTextBlock(FieldBlock):
@cached_property
def field(self):
@ -367,6 +379,10 @@ class RichTextBlock(FieldBlock):
def render_basic(self, value):
return mark_safe('<div class="rich-text">' + expand_db_html(value) + '</div>')
def get_searchable_content(self, value):
return [force_text(value)]
class RawHTMLBlock(FieldBlock):
def __init__(self, required=True, help_text=None, max_length=None, min_length=None, **kwargs):
self.field = forms.CharField(
@ -542,6 +558,14 @@ class BaseStructBlock(Block):
for name, val in value.items()
])
def get_searchable_content(self, value):
content = []
for name, block in self.child_blocks.items():
content.extend(block.get_searchable_content(value.get(name, block.meta.default)))
return content
def deconstruct(self):
"""
Always deconstruct StructBlock instances as if they were plain StructBlocks with all of the
@ -747,6 +771,14 @@ class ListBlock(Block):
)
return format_html("<ul>{0}</ul>", children)
def get_searchable_content(self, value):
content = []
for child_value in value:
content.extend(self.child_block.get_searchable_content(child_value))
return content
# ===========
# StreamBlock
@ -925,6 +957,14 @@ class BaseStreamBlock(Block):
[(child, child.block_type) for child in value]
)
def get_searchable_content(self, value):
content = []
for child in value:
content.extend(child.block.get_searchable_content(child.value))
return content
def deconstruct(self):
"""
Always deconstruct StreamBlock instances as if they were plain StreamBlocks with all of the

View file

@ -82,3 +82,6 @@ class StreamField(with_metaclass(models.SubfieldBase, models.Field)):
def value_to_string(self, obj):
value = self._get_val_from_obj(obj)
return self.get_prep_value(value)
def get_searchable_content(self, value):
return self.stream_block.get_searchable_content(value)

View file

@ -35,6 +35,12 @@ class TestFieldBlock(unittest.TestCase):
self.assertIn('This field is required.', html)
def test_charfield_searchable_content(self):
block = blocks.CharBlock()
content = block.get_searchable_content("Hello world!")
self.assertEqual(content, ["Hello world!"])
def test_choicefield_render(self):
class ChoiceBlock(blocks.FieldBlock):
field = forms.ChoiceField(choices=(
@ -62,6 +68,19 @@ class TestFieldBlock(unittest.TestCase):
self.assertIn('<option value="choice-1">Choice 1</option>', html)
self.assertIn('<option value="choice-2" selected="selected">Choice 2</option>', html)
@unittest.expectedFailure # Returning "choice-1" instead of "Choice 1"
def test_choicefield_searchable_content(self):
class ChoiceBlock(blocks.FieldBlock):
field = forms.ChoiceField(choices=(
('choice-1', "Choice 1"),
('choice-2', "Choice 2"),
))
block = ChoiceBlock()
content = block.get_searchable_content("choice-1")
self.assertEqual(content, ["Choice 1"])
class TestMeta(unittest.TestCase):
def test_set_template_with_meta(self):
@ -262,6 +281,19 @@ class TestStructBlock(unittest.TestCase):
block = LinkBlock()
self.assertIn('<script type="text/x-html-template">hello world</script>', block.all_html_declarations())
def test_searchable_content(self):
class LinkBlock(blocks.StructBlock):
title = blocks.CharBlock()
link = blocks.URLBlock()
block = LinkBlock()
content = block.get_searchable_content({
'title': "Wagtail site",
'link': 'http://www.wagtail.io',
})
self.assertEqual(content, ["Wagtail site"])
class TestListBlock(unittest.TestCase):
def test_initialise_with_class(self):
@ -398,6 +430,25 @@ class TestListBlock(unittest.TestCase):
block = blocks.ListBlock(CharBlockWithDeclarations())
self.assertIn('<script type="text/x-html-template">hello world</script>', block.all_html_declarations())
def test_searchable_content(self):
class LinkBlock(blocks.StructBlock):
title = blocks.CharBlock()
link = blocks.URLBlock()
block = blocks.ListBlock(LinkBlock())
content = block.get_searchable_content([
{
'title': "Wagtail",
'link': 'http://www.wagtail.io',
},
{
'title': "Django",
'link': 'http://www.djangoproject.com',
},
])
self.assertEqual(content, ["Wagtail", "Django"])
class TestStreamBlock(unittest.TestCase):
def test_initialisation(self):
@ -646,3 +697,32 @@ class TestStreamBlock(unittest.TestCase):
block_value = block.value_from_datadict(post_data, {}, 'article')
self.assertEqual(block_value[2].value, "heading 2")
def test_searchable_content(self):
class ArticleBlock(blocks.StreamBlock):
heading = blocks.CharBlock()
paragraph = blocks.CharBlock()
block = ArticleBlock()
value = block.to_python([
{
'type': 'heading',
'value': "My title",
},
{
'type': 'paragraph',
'value': 'My first paragraph',
},
{
'type': 'paragraph',
'value': 'My second paragraph',
},
])
content = block.get_searchable_content(value)
self.assertEqual(content, [
"My title",
"My first paragraph",
"My second paragraph",
])

View file

@ -116,7 +116,10 @@ class BaseField(object):
def get_value(self, obj):
try:
field = self.get_field(obj.__class__)
return field._get_val_from_obj(obj)
value = field._get_val_from_obj(obj)
if hasattr(field, 'get_searchable_content'):
value = field.get_searchable_content(value)
return value
except models.fields.FieldDoesNotExist:
value = getattr(obj, self.field_name, None)
if hasattr(value, '__call__'):