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
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
springboard
springboard>=1.0.0
5 changes: 5 additions & 0 deletions springboard_iogt/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/',
Expand Down
35 changes: 10 additions & 25 deletions springboard_iogt/templates/home.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -15,41 +15,26 @@
</div>
</div>

{% set featured_pages = all_pages.filter(language=language, featured=True) %}
{% if featured_pages %}
<div class="articles latest">
<div class="h1">{{gettext('Latest')}}</div>
{% for page in featured_pages %}
{% for category, page in recent_content(limit=8) %}
<div class="articles">
{% if category %}
<div class="h1">
<a href="{{'category'|route_url(uuid=category.uuid)}}/">{{category.title}}</a>
</div>
{% endif %}
<div class="listing no-pic">
<div class="h2"><a href="{{'page'|route_url(uuid=page.uuid)}}">{{page.title}}</a></div>
<p>{{page.description}}</p>
<div class="clr"></div>
</div>
{% endfor %}
</div>
{% endif %}

{% for category in all_categories.filter(language=language) %}
{% set category_pages = all_pages.filter(primary_category=category.uuid) %}
{% if category_pages %}
<div class="articles">
<div class="h1">
<a href="{{'category'|route_url(uuid=category.uuid)}}/">{{category.title}}</a>
</div>
{% for page in category_pages %}
<div class="listing no-pic">
<div class="h2"><a href="{{'page'|route_url(uuid=page.uuid)}}">{{page.title}}</a></div>
<p>{{page.description}}</p>
<div class="clr"></div>
</div>
{% endfor %}
<div class="clr"></div>
{% if category %}
<div class="pagination">
<a href="{{'category'|route_url(uuid=category.uuid)}}"><span>{{gettext('More')}}</span> {{category.title}}</a>
</div>
{% endif %}
</div>
{% endif %}
{% endfor %}
</div>
</div>
{% endblock %}
{% endblock %}
Empty file.
70 changes: 70 additions & 0 deletions springboard_iogt/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -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)
8 changes: 8 additions & 0 deletions springboard_iogt/utils.py
Original file line number Diff line number Diff line change
@@ -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"
}})
65 changes: 65 additions & 0 deletions springboard_iogt/views.py
Original file line number Diff line number Diff line change
@@ -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
Copy link

Choose a reason for hiding this comment

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

Are we returning a function here to try & not evaluate this stuff unless called explicitly in the template?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That, but mostly I want the template to decide how many items are needed.

Copy link

Choose a reason for hiding this comment

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

Would it be a good idea to turn this into a re-usable macro / filter for Jinja2 then?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can give it a shot, but can I postpone doing that until change requests come in?

Copy link

Choose a reason for hiding this comment

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

👍