Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions springboard/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ def includeme(config):
config.add_route('home', '/')
config.add_route('category', '/category/{uuid}/')
config.add_route('page', '/page/{uuid}/')
config.add_route('search', '/search/')
config.add_route('locale', '/locale/')
config.add_route('locale_change', '/locale/change/')
config.add_route('locale_matched', '/locale/{language}/')
Expand Down
1 change: 1 addition & 0 deletions springboard/defaults.ini
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jinja2.filters =
display_language_name = springboard.filters:display_language_name_filter
language_direction = springboard.filters:language_direction_filter
paginate = springboard.filters:paginate_filter
get_category_title = springboard.filters:get_category_title_filter

unicorehub.host =
unicorehub.app_id =
Expand Down
7 changes: 7 additions & 0 deletions springboard/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,10 @@ def language_direction_filter(locale):

def paginate_filter(results, page, results_per_page=10, slider_value=5):
return Paginator(results, page, results_per_page, slider_value)


@contextfilter
def get_category_title_filter(ctx, primary_category_uuid, all_categories):
for category in all_categories:
if primary_category_uuid == category.uuid:
return category.title
3 changes: 3 additions & 0 deletions springboard/templates/atoms.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{% macro image(src, srcset, alt) -%}
<img src="{{src}}" srcset="{{srcset}}" alt="{{alt}}">
{%- endmacro %}
6 changes: 6 additions & 0 deletions springboard/templates/base.jinja2
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{% import 'molecules.html' as molecules %}

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
Expand Down Expand Up @@ -46,5 +48,9 @@
{% endblock %}
</div>

{% block search %}
{{molecules.search_box(view)}}
{% endblock %}

</body>
</html>
123 changes: 123 additions & 0 deletions springboard/templates/molecules.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
{% import 'atoms.html' as atoms %}

{% macro list_of_languages(view, languages, styling_classes) -%}
<ul class="nav list_of_languages list-inline {{styling_classes}}" role="navigation">
{% for lang_code, lang_name in languages %}
<li>
<a href="{{'locale_matched'|route_url(language=lang_code)}}">{{lang_name}}</a>
</li>
{% endfor %}
<li><a href={{'locale_change'|route_url}}>&gt;&gt;</a></li>
</ul>
{%- endmacro %}

{% macro logo(view, src, styling_classes) -%}
<div class="logo {{styling_classes}}">
<a href={{view.request.route_url('home')}}>
{{atoms.image(src, "", "")}}
</a>
</div>
{%- endmacro %}

{% macro banner_with_image(src) -%}
<div class ="banner">
{{ atoms.image(src, "", "")}}
</div>
{%- endmacro %}

<!-- Assumed that list always includes home -->
{% macro list_of_site_links(view, dict, styling_classes) -%}
<ul class="nav {{styling_classes}}" role="navigation">
<li><a href={{view.request.route_url('home')}}>Home</a></li>
{% for key, value in dict %}
<li><a href={{view.request.route_url('flatpage', slug=key)}}>{{value}}</a></li>
{% endfor %}
</ul>
{%- endmacro %}

{% macro image_and_heading(view, cat, styling_classes) -%}
<div class="{{styling_classes}}">
{{atoms.image( view.get_image_url(cat.image_host, cat.image, 45, 45), "" , "" ) }}
<h3>{{cat.title}}</h3>
</div>
{%- endmacro %}

{% macro footer(message) -%}
<div class="footer" role="contentinfo">
&copy; {{message}}
</div>
{%- endmacro %}

{% macro search_box(view) -%}
<form action={{view.request.route_url('search')}} method='GET'>
<label for="q">Search:
<input id="q" name="q" type="text">
</label>
<input type="submit" value='Submit' />
</form>
{%- endmacro %}

{% macro page_number_navigator(view, paginator, p, query) -%}

{% if paginator.has_previous_page() %}
<a href={{view.request.route_url('search')}}?q={{query}}&p={{p-1}} >&lt;&nbsp;Previous</a>
{% endif %}

{% if paginator.show_start() %}
<a href={{view.request.route_url('search')}}?q={{query}}&p=0>1</a>
{% endif %}

{% if paginator.needs_start_ellipsis() %}
<a>...</a>
{% endif %}

{% if paginator.page_numbers_left() %}
{% for number in paginator.page_numbers_left() %}
<a href={{view.request.route_url('search')}}?q={{query}}&p={{number}}>{{number + 1}}</a>
{% endfor %}
{% endif %}

<b>{{p + 1}}</b>

{% if paginator.page_numbers_right() %}
{% for number in paginator.page_numbers_right() %}
<a href={{view.request.route_url('search')}}?q={{query}}&p={{number}}>{{number + 1}}</a>
{% endfor %}
{% endif %}

{% if paginator.needs_end_ellipsis() %}
<a>...</a>
{% endif %}

{% if paginator.show_end() %}
<a href={{view.request.route_url('search')}}?q={{query}}&p={{paginator.total_pages()-1}}>{{paginator.total_pages()}}</a>
{% endif %}

{% if paginator.has_next_page() %}
<a href={{view.request.route_url('search')}}?q={{query}}&p={{p + 1}}>Next&nbsp;&gt;</a>
{% endif %}

{%- endmacro %}

{% macro message_for_no_results(query) -%}
{% if query %}
<p>No results found for <b>{{query}}</b></p>
{% else %}
<p>No results found!</p>
{% endif %}
{%- endmacro %}

{% macro search_summary(num_of_results, query) -%}
{% if num_of_results==1 %}
<p>1 search result for: <b>{{query}}</b></p>
{% elif num_of_results > 1 %}
<p>{{num_of_results}} search results for: <b>{{query}}</b></p>
{% endif %}
{%- endmacro %}

{% macro article_summary(result, all_categories) -%}
{% if result.primary_category %}
<p>{{result.primary_category|get_category_title(all_categories) }}</p>
{% endif %}
<a href="{{'page'|route_url(uuid=result.uuid)}}">{{result.title}} ... Read More &gt;</a>
{%- endmacro %}
12 changes: 12 additions & 0 deletions springboard/templates/organisms.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{% import 'molecules.html' as molecules %}
{% import 'atoms.html' as atoms %}

{% macro list_of_returned_articles(paginator, all_categories) -%}
<ul>
{% for result in paginator.get_page() %}
<li>
{{molecules.article_summary(result, all_categories)}}
</li>
{% endfor %}
</ul>
{%- endmacro %}
23 changes: 23 additions & 0 deletions springboard/templates/search_results.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{% extends "base.jinja2" %}
{% import 'atoms.html' as atoms %}
{% import 'molecules.html' as molecules %}
{% import 'organisms.html' as organisms %}

{% block content %}
<div class="content">

{% if paginator %}

{{molecules.search_summary(paginator.total_count(), query)}}

{% if paginator.total_pages() > 1 %}
{{molecules.page_number_navigator(view, paginator, p, query)}}
{% endif %}

{{organisms.list_of_returned_articles(paginator, all_categories)}}
{% else %}
{{molecules.message_for_no_results(query)}}
{% endif %}

</div>
{% endblock %}
115 changes: 115 additions & 0 deletions springboard/tests/test_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
from springboard.tests import SpringboardTestCase


from pyramid import testing
from unicore.content.models import Page


class TestSearch(SpringboardTestCase):

def setUp(self):
self.workspace = self.mk_workspace()
settings = {
'unicore.repos_dir': self.working_dir,
'unicore.content_repo_urls': self.workspace.working_dir,
'available_languages': '\n'.join([
'eng_GB',
'swa_KE',
'spa_ES',
]),
'featured_languages': '\n'.join([
'spa_ES',
'eng_GB',
])
}
self.config = testing.setUp(settings=settings)
self.app = self.mk_app(self.workspace, settings=settings)

def tearDown(self):
testing.tearDown()

def test_search_no_results(self):
self.app = self.mk_app(self.workspace)

resp = self.app.get('/search/', params={'q': ''}, status=200)
self.assertTrue('No results found' in resp.body)

def test_search_blank(self):
self.app = self.mk_app(self.workspace)
self.mk_pages(self.workspace)

resp = self.app.get('/search/', params={'q': None}, status=200)
self.assertTrue('No results found' in resp.body)

def test_search_2_results(self):
self.app = self.mk_app(self.workspace)
self.mk_pages(self.workspace, count=2)
resp = self.app.get('/search/', params={'q': 'sample'}, status=200)

self.assertFalse('No results found' in resp.body)
self.assertTrue('Test Page 0' in resp.body)
self.assertTrue('Test Page 1' in resp.body)

def test_search_multiple_results(self):
self.app = self.mk_app(self.workspace)
self.mk_pages(self.workspace, count=11)
resp = self.app.get('/search/', params={'q': 'sample'}, status=200)
self.assertTrue(
'Next&nbsp;&gt;</a>'
in resp.body)

def test_search_profanity(self):
self.app = self.mk_app(self.workspace)
self.mk_pages(self.workspace, count=2)

resp = self.app.get('/search/', params={'q': 'kak'}, status=200)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are we testing here exactly?


self.assertTrue('No results found' in resp.body)

def test_search_added_page(self):
self.app = self.mk_app(self.workspace)
mother_page = Page({
'title': 'title for mother', 'language': 'eng_GB', 'position': 2,
'content': 'Page for mother test page'})
self.workspace.save(mother_page, 'Add mother page')

self.workspace.refresh_index()

resp = self.app.get('/search/', params={'q': 'mother'}, status=200)

self.assertTrue('mother' in resp.body)
self.assertFalse('No results found' in resp.body)

def test_pagination(self):
self.app = self.mk_app(self.workspace)
self.mk_pages(self.workspace, count=15, content='baby')
resp = self.app.get(
'/search/', params={'q': 'baby', 'p': '0'}, status=200)
self.assertFalse('Previous' in resp.body)
self.assertTrue('Next' in resp.body)

def test_search_language_filter(self):
[category_eng] = self.mk_categories(
self.workspace, count=1, language='eng_GB',
title='English Category')
self.mk_pages(
self.workspace, count=1, language='eng_GB',
primary_category=category_eng.uuid,
content='Page for mother test page')
[category_spa] = self.mk_categories(
self.workspace, count=1, language='spa_ES',
title='Spanish Category')
self.mk_pages(
self.workspace, count=1, language='spa_ES',
primary_category=category_spa.uuid,
content='Page for mother test page')

self.app.get('/locale/?language=eng_GB', status=302)
resp = self.app.get('/search/', params={'q': 'mother'}, status=200)
self.assertTrue('English Category' in resp.body)
self.assertFalse('Spanish Category' in resp.body)

self.app.get('/locale/?language=spa_ES', status=302)
resp = self.app.get('/search/', params={'q': 'mother'}, status=200)
self.assertTrue('Spanish Category' in resp.body)
self.assertFalse('English Category' in resp.body)
1 change: 0 additions & 1 deletion springboard/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ def __init__(self, request):
self.all_pages = SM(Page, **search_config).es(**self.es_settings)
self.all_localisations = SM(Localisation, **search_config).es(
**self.es_settings)

self.available_languages = config_list(
self.settings.get('available_languages', ''))
self.featured_languages = config_list(
Expand Down
46 changes: 45 additions & 1 deletion springboard/views/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from pyramid.httpexceptions import HTTPFound
from pyramid.response import Response

from springboard.utils import ga_context
from springboard.utils import ga_context, Paginator
from springboard.views.base import SpringboardViews

from unicore.distribute.tasks import fastforward
Expand All @@ -29,6 +29,50 @@ def category(self):
[category] = self.all_categories.filter(uuid=uuid)
return self.context(category=category)

@view_config(route_name='search',
renderer='springboard:templates/search_results.jinja2')
def search(self):

query = self.request.GET.get('q')
p = int(self.request.GET.get('p', 0))

empty_defaults = self.context(
paginator=[],
query=query,
p=p,
)

# handle query exception
if not query:
return empty_defaults

all_results = self.all_pages.query(
content__query_string=query).filter(language=self.language)

# no results found
if all_results.count() == 0:
return empty_defaults

paginator = Paginator(all_results, p)

# requested page number is out of range
total_pages = paginator.total_pages()
# sets the floor to 0
p = p if p >= 0 else 0
# sets the roof to `total_pages -1`
p = p if p < total_pages else total_pages - 1
paginator = Paginator(all_results, p)

relevant_categories = self.all_categories.query().filter(
language=self.language)

return self.context(
relevant_categories=relevant_categories,
paginator=paginator,
query=query,
p=p,
)

@ga_context(lambda context: {'dt': context['page'].title, })
@view_config(route_name='page',
renderer='springboard:templates/page.jinja2')
Expand Down