Skip to content

Commit

Permalink
First version of project rank in project index
Browse files Browse the repository at this point in the history
  • Loading branch information
alejandrodob committed May 29, 2015
1 parent e5e7fd2 commit e999c28
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 7 deletions.
49 changes: 47 additions & 2 deletions pybossa/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,6 @@ def get_port():

def get_user_id_or_ip():
"""Return the id of the current user if is authenticated.
Otherwise returns its IP address (defaults to 127.0.0.1).
"""
user_id = current_user.id if current_user.is_authenticated() else None
Expand All @@ -366,7 +365,6 @@ def get_user_id_or_ip():

def with_cache_disabled(f):
"""Decorator that disables the cache for the execution of a function.
It enables it back when the function call is done.
"""
import os
Expand Down Expand Up @@ -401,3 +399,50 @@ def username_from_full_name(username):
if type(username) == str:
return username.decode('ascii', 'ignore').lower().replace(' ', '')
return username.encode('ascii', 'ignore').decode('utf-8').lower().replace(' ', '')


def rank(projects):
"""Takes a list of (published) projects (as dicts) and orders them by
activity, number of volunteers, number of tasks and other criteria."""
def earned_points(project):
points = 0
if project['info'].get('thumbnail'):
points += 50
if project['overall_progress'] != 100L:
points += 50
if 'test' in project['name'] or 'test' in project['short_name']:
points -= 50
points += _points_by_number_of_tasks(project['n_tasks'])
points += _points_by_number_of_crafters(project['n_volunteers'])
return points
for project in projects:
project['points'] = earned_points(project)
print earned_points(project)
projects.sort(key=lambda p: p['points'], reverse=True)
return projects

def _points_by_number_of_tasks(n_tasks):
if n_tasks > 100:
return 20
if n_tasks > 50:
return 15
if n_tasks > 20:
return 10
if n_tasks > 10:
return 5
if n_tasks > 0:
return 1
return 0

def _points_by_number_of_crafters(n_volunteers):
if n_volunteers > 100:
return 20*2
if n_volunteers > 50:
return 15*2
if n_volunteers > 20:
return 10*2
if n_volunteers > 10:
return 5*2
if n_volunteers > 0:
return 1*2
return 0
13 changes: 8 additions & 5 deletions pybossa/view/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from pybossa.model.task_run import TaskRun
from pybossa.model.auditlog import Auditlog
from pybossa.model.blogpost import Blogpost
from pybossa.util import Pagination, admin_required, get_user_id_or_ip
from pybossa.util import Pagination, admin_required, get_user_id_or_ip, rank
from pybossa.auth import ensure_authorized_to
from pybossa.cache import projects as cached_projects
from pybossa.cache import categories as cached_cat
Expand Down Expand Up @@ -117,7 +117,7 @@ def redirect_old_draft(page):
def index(page):
"""List projects in the system"""
if cached_projects.n_count('featured') > 0:
return project_index(page, cached_projects.get_featured, 'featured',
return project_index(page, cached_projects.get_all_featured, 'featured',
True, False)
else:
categories = cached_cat.get_all()
Expand All @@ -130,7 +130,10 @@ def project_index(page, lookup, category, fallback, use_count):

per_page = current_app.config['APPS_PER_PAGE']

projects = lookup(category, page, per_page)
ranked_projects = rank(lookup(category))
offset = (page - 1) * per_page
projects = ranked_projects[offset:offset+per_page]

count = cached_projects.n_count(category)

data = []
Expand Down Expand Up @@ -174,15 +177,15 @@ def project_index(page, lookup, category, fallback, use_count):
@admin_required
def draft(page):
"""Show the Draft projects"""
return project_index(page, cached_projects.get_draft, 'draft',
return project_index(page, cached_projects.get_all_draft, 'draft',
False, True)


@blueprint.route('/category/<string:category>/', defaults={'page': 1})
@blueprint.route('/category/<string:category>/page/<int:page>/')
def project_cat_index(category, page):
"""Show Projects that belong to a given category"""
return project_index(page, cached_projects.get, category, False, True)
return project_index(page, cached_projects.get_all, category, False, True)


@blueprint.route('/new', methods=['GET', 'POST'])
Expand Down
57 changes: 57 additions & 0 deletions test/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,3 +330,60 @@ def test_it_removes_non_ascii_chars_unicode(self):
obtained = util.username_from_full_name(name)

assert obtained == expected_username, obtained


class TestRankProjects(object):

def test_it_gives_priority_to_projects_with_an_avatar(self):
projects = [{'info': {}, 'n_tasks': 4L, 'short_name': u'avatar', 'created': u'2015-04-10T11:36:14.814137', 'n_volunteers': 0L, 'updated': u'2015-05-27T11:06:52.530033', 'last_activity_raw': None, 'overall_progress': 0L, 'id': 19, 'name': u'without avatar'},
{'info': {u'container': u'user_7', u'thumbnail': u'avatar.png'}, 'n_tasks': 246L, 'short_name': u'noavatar', 'created': u'2015-05-27T10:55:06.922018', 'n_volunteers': 0L, 'updated': u'2015-05-27T11:06:51.705126', 'last_activity_raw': None, 'overall_progress': 0L, 'id': 48, 'name': u'with avatar'}]
ranked = util.rank(projects)

assert ranked[0]['name'] == "with avatar"
assert ranked[1]['name'] == "without avatar"

def test_it_gives_priority_to_uncompleted_projects(self):
projects = [{'info': {}, 'n_tasks': 4L, 'short_name': 'uncompleted', 'name': u'uncompleted', 'overall_progress': 0L, 'n_volunteers': 1L},
{'info': {}, 'n_tasks': 4L, 'short_name': 'completed', 'name': u'completed', 'overall_progress': 100L, 'n_volunteers': 1L}]
ranked = util.rank(projects)

assert ranked[0]['name'] == "uncompleted"
assert ranked[1]['name'] == "completed"

def test_it_penalizes_projects_with_test_in_the_name_or_short_name(self):
projects = [{'info': {}, 'n_tasks': 4L, 'name': u'my test 123', 'short_name': u'123', 'overall_progress': 0L, 'n_volunteers': 1L},
{'info': {}, 'n_tasks': 246L, 'name': u'123', 'short_name': u'mytest123', 'overall_progress': 0L, 'n_volunteers': 1L},
{'info': {}, 'n_tasks': 246L, 'name': u'real', 'short_name': u'real', 'overall_progress': 0L, 'n_volunteers': 1L}]
ranked = util.rank(projects)

assert ranked[0]['name'] == "real"

def test_rank_by_number_of_tasks(self):
projects = [{'info': {}, 'n_tasks': 1L, 'name': u'last', 'short_name': u'a', 'overall_progress': 0L, 'n_volunteers': 1L},
{'info': {}, 'n_tasks': 11L, 'name': u'fourth', 'short_name': u'b', 'overall_progress': 0L, 'n_volunteers': 1L},
{'info': {}, 'n_tasks': 21L, 'name': u'third', 'short_name': u'c', 'overall_progress': 0L, 'n_volunteers': 1L},
{'info': {}, 'n_tasks': 51L, 'name': u'second', 'short_name': u'd', 'overall_progress': 0L, 'n_volunteers': 1L},
{'info': {}, 'n_tasks': 101L, 'name': u'first', 'short_name': u'e', 'overall_progress': 0L, 'n_volunteers': 1L}]
ranked = util.rank(projects)

assert ranked[0]['name'] == 'first'
assert ranked[1]['name'] == 'second'
assert ranked[2]['name'] == 'third'
assert ranked[3]['name'] == 'fourth'
assert ranked[4]['name'] == 'last'

def test_rank_by_number_of_crafters(self):
projects = [{'info': {}, 'n_tasks': 1L, 'name': u'last', 'short_name': u'a', 'overall_progress': 0L, 'n_volunteers': 0L},
{'info': {}, 'n_tasks': 1L, 'name': u'fifth', 'short_name': u'b', 'overall_progress': 0L, 'n_volunteers': 1L},
{'info': {}, 'n_tasks': 1L, 'name': u'fourth', 'short_name': u'b', 'overall_progress': 0L, 'n_volunteers': 11L},
{'info': {}, 'n_tasks': 1L, 'name': u'third', 'short_name': u'c', 'overall_progress': 0L, 'n_volunteers': 21L},
{'info': {}, 'n_tasks': 1L, 'name': u'second', 'short_name': u'd', 'overall_progress': 0L, 'n_volunteers': 51L},
{'info': {}, 'n_tasks': 1L, 'name': u'first', 'short_name': u'e', 'overall_progress': 0L, 'n_volunteers': 101L}]
ranked = util.rank(projects)

assert ranked[0]['name'] == 'first'
assert ranked[1]['name'] == 'second'
assert ranked[2]['name'] == 'third'
assert ranked[3]['name'] == 'fourth'
assert ranked[4]['name'] == 'fifth'
assert ranked[5]['name'] == 'last'

0 comments on commit e999c28

Please sign in to comment.