diff --git a/requirements.txt b/requirements.txt index 26c7d07..18d0948 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -springboard \ No newline at end of file +springboard>=1.0.0 diff --git a/springboard_iogt/application.py b/springboard_iogt/application.py index 1694536..87ea382 100644 --- a/springboard_iogt/application.py +++ b/springboard_iogt/application.py @@ -13,6 +13,11 @@ def main(global_config, **settings): defaults.update(settings) config = Configurator(settings=defaults) + + # override springboard routes + config.add_route('home', '/') + config.scan('.views') + config.include('springboard.config') config.override_asset( to_override='springboard:templates/', diff --git a/springboard_iogt/templates/home.jinja2 b/springboard_iogt/templates/home.jinja2 index 48a3554..d7963a7 100644 --- a/springboard_iogt/templates/home.jinja2 +++ b/springboard_iogt/templates/home.jinja2 @@ -15,41 +15,26 @@ - {% set featured_pages = all_pages.filter(language=language, featured=True) %} - {% if featured_pages %} -
-
{{gettext('Latest')}}
- {% for page in featured_pages %} + {% for category, page in recent_content(limit=8) %} +
+ {% if category %} + + {% endif %}

{{page.description}}

- {% endfor %} -
- {% endif %} - - {% for category in all_categories.filter(language=language) %} - {% set category_pages = all_pages.filter(primary_category=category.uuid) %} - {% if category_pages %} -
- - {% for page in category_pages %} -
- -

{{page.description}}

-
-
- {% endfor %}
+ {% if category %} + {% endif %}
- {% endif %} {% endfor %}
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/springboard_iogt/tests/__init__.py b/springboard_iogt/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/springboard_iogt/tests/test_views.py b/springboard_iogt/tests/test_views.py new file mode 100644 index 0000000..9fa88f6 --- /dev/null +++ b/springboard_iogt/tests/test_views.py @@ -0,0 +1,70 @@ +import re + +from datetime import datetime, timedelta + +from pyramid import testing + +from springboard.tests import SpringboardTestCase + +from springboard_iogt.views import IoGTViews +from springboard_iogt.application import main + + +class TestIoGTViews(SpringboardTestCase): + + def setUp(self): + self.workspace = self.mk_workspace() + self.config = testing.setUp(settings={ + 'unicore.repos_dir': self.working_dir, + 'unicore.content_repo_urls': self.workspace.working_dir, + }) + + def tearDown(self): + testing.tearDown() + + def test_recent_content(self): + category_p1, category_p3 = self.mk_categories(self.workspace, count=2) + [page1] = self.mk_pages( + self.workspace, count=1, + primary_category=category_p1.uuid, + created_at=datetime.utcnow().isoformat()) + [page2] = self.mk_pages( + self.workspace, count=1, + primary_category=None, + created_at=(datetime.utcnow() - timedelta(hours=1)).isoformat()) + [page3] = self.mk_pages( + self.workspace, count=1, + primary_category=category_p3.uuid, + created_at=(datetime.utcnow() - timedelta(hours=2)).isoformat()) + views = IoGTViews(self.mk_request()) + + results = views.recent_content()(limit=2) + self.assertEqual(len(results), 2) + self.assertEqual( + {(category_p1.uuid, page1.uuid), (None, page2.uuid)}, + set((c.uuid if c else None, p.uuid) for c, p in results)) + + results = views.recent_content()(limit=3) + self.assertEqual(len(results), 3) + self.assertEqual( + {(category_p1.uuid, page1.uuid), (None, page2.uuid), + (category_p3.uuid, page3.uuid)}, + set((c.uuid if c else None, p.uuid) for c, p in results)) + + def test_index_view(self): + [category] = self.mk_categories(self.workspace, count=1) + [page1, page2] = self.mk_pages( + self.workspace, count=2, + created_at=datetime.utcnow().isoformat()) + page1 = page1.update({'primary_category': category.uuid}) + self.workspace.save(page1, 'Update page category') + self.workspace.refresh_index() + app = self.mk_app(self.workspace, main=main) + + response = app.get('/') + self.assertEqual(response.status_int, 200) + html = response.html + re_page_url = re.compile(r'/page/.{32}/') + re_category_url = re.compile(r'/category/.{32}/') + self.assertEqual(len(html.find_all('a', href=re_page_url)), 2) + self.assertEqual(len(html.find_all('a', href=re_category_url)), 2) diff --git a/springboard_iogt/utils.py b/springboard_iogt/utils.py new file mode 100644 index 0000000..ae9b7de --- /dev/null +++ b/springboard_iogt/utils.py @@ -0,0 +1,8 @@ +def randomize_query(s_obj, seed=None): + return s_obj.query_raw({ + "function_score": { + "functions": [ + {"random_score": {"seed": seed}} + ], + "score_mode": "sum" + }}) diff --git a/springboard_iogt/views.py b/springboard_iogt/views.py new file mode 100644 index 0000000..dfe3418 --- /dev/null +++ b/springboard_iogt/views.py @@ -0,0 +1,65 @@ +import random +from datetime import datetime +from itertools import chain + +from pyramid.view import view_config +from elasticutils import F + +from springboard.views.base import SpringboardViews + +from springboard_iogt.utils import randomize_query + + +class IoGTViews(SpringboardViews): + + @view_config(route_name='home', + renderer='springboard_iogt:templates/home.jinja2') + def index_view(self): + return self.context(recent_content=self.recent_content()) + + def recent_content(self): + # get 2 most recent pages and their categories + [page1, page2] = self.all_pages.filter( + language=self.language).order_by('-created_at')[:2] + categories = self.all_categories.filter( + uuid__in=filter( + None, [page1.primary_category, page2.primary_category])) + categories = dict((category.uuid, category) for category in categories) + category1 = categories.get(page1.primary_category) + category2 = categories.get(page2.primary_category) + + # random seed that changes hourly + seed = datetime.utcnow().hour + + # get random categories and exclude categories of 2 most recent pages + categories = self.all_categories.filter( + ~F(uuid__in=categories.keys()), language=self.language) + categories = randomize_query(categories, seed=seed) + # filter to exclude the 2 most recent pages + f_exclude_pages = ~F(uuid__in=[page1.uuid, page2.uuid]) + + def do_query(limit): + most_recent = [(category1, page1), (category2, page2)] + most_recent_per_category = [] + # NOTE: this is bad if limit is large + # We are fetching pages individually, because we + # can't use facets or aggregates. + for category in categories[:limit - 2]: + try: + [page] = self.all_pages.filter( + f_exclude_pages, + primary_category=category.uuid).order_by( + '-created_at')[:1] + most_recent_per_category.append((category, page)) + except ValueError: + pass + + results = [(c.to_object() if c else None, p.to_object()) + for c, p + in chain(most_recent, most_recent_per_category)] + # randomize position of most_recent content + random.seed(seed) + random.shuffle(results) + return results + + return do_query