-
{{ _('News feed') }} {{ _('Activity from users and datasets that you follow') }}
- {{ c.dashboard_activity_stream }}
+{% block primary %}
+
+
+
+ {% snippet 'user/snippets/followee_dropdown.html', context=c.dashboard_activity_stream_context, followees=c.followee_list %}
+
+ {{ _('News feed') }}
+ {{ _('Activity from items that you follow') }}
+
+ {{ c.dashboard_activity_stream }}
+
+
{% endblock %}
-{% block secondary_content %}
-
- {{ _('My Datasets') }}
- {% if c.user_dict['datasets'] %}
-
- {% else %}
- {{ _('You currently have not added any datasets yet') }}
- {% endif %}
-
-{% endblock %}
+{% block sidebar %}{% endblock %}
diff --git a/ckan/templates/user/snippets/followee_dropdown.html b/ckan/templates/user/snippets/followee_dropdown.html
new file mode 100644
index 00000000000..c3daffbce64
--- /dev/null
+++ b/ckan/templates/user/snippets/followee_dropdown.html
@@ -0,0 +1,45 @@
+{% macro followee_icon(type) -%}
+ {% if type == 'dataset' %}
+
+ {% elif type == 'user' %}
+
+ {% elif type == 'group' %}
+
+ {% endif %}
+{%- endmacro %}
+
+
diff --git a/ckan/tests/functional/api/test_activity.py b/ckan/tests/functional/api/test_activity.py
index 51bee6de429..e5473df8d4c 100644
--- a/ckan/tests/functional/api/test_activity.py
+++ b/ckan/tests/functional/api/test_activity.py
@@ -17,7 +17,6 @@
from ckan.lib.helpers import json
-##<<<<<<< HEAD
##def package_update(context, data_dict):
## # These tests call package_update directly which is really bad
## # setting api_version in context make things seem like the api key
@@ -33,7 +32,6 @@
## context['api_version'] = 3
## context['ignore_auth'] = True
## return _package_create(context, data_dict)
-##=======
def package_show(app, data_dict, apikey=None):
if apikey:
extra_environ = {'Authorization': str(apikey)}
@@ -2085,10 +2083,43 @@ def test_follow_user(self):
#if 'id' not in activity:
# assert False, "activity object should have an id value"
# TODO: Test for the _correct_ revision_id value.
+
#if 'revision_id' not in activity:
# assert False, "activity object should have a revision_id value"
#timestamp = datetime_from_string(activity['timestamp'])
#assert timestamp >= before['time'] and timestamp <= \
# after['time'], str(activity['timestamp'])
- #
+
#assert len(self.activity_details(activity)) == 0
+
+ def test_user_activity_list_by_name(self):
+ '''user_activity_list should accept a user name as param.'''
+ import ckan.tests
+ activities = ckan.tests.call_action_api(self.app, 'user_activity_list',
+ id='annafan')
+ assert len(activities) > 0
+
+ def test_package_activity_list_by_name(self):
+ '''package_activity_list should accept a package name as param.'''
+ import ckan.tests
+ activities = ckan.tests.call_action_api(self.app,
+ 'package_activity_list', id='warandpeace',
+ apikey=self.sysadmin_user['apikey'])
+ assert len(activities) > 0
+
+ def test_group_activity_list_by_name(self):
+ '''group_activity_list should accept a group name as param.'''
+ import ckan.tests
+ activities = ckan.tests.call_action_api(self.app,
+ 'group_activity_list', id='roger')
+ assert len(activities) > 0
+
+ def test_organization_activity_list_by_name(self):
+ '''organization_activity_list should accept a org name as param.'''
+ import ckan.tests
+ organization = ckan.tests.call_action_api(self.app,
+ 'organization_create', name='test_org',
+ apikey=self.sysadmin_user['apikey'])
+ activities = ckan.tests.call_action_api(self.app,
+ 'organization_activity_list', id=organization['name'])
+ assert len(activities) > 0
diff --git a/ckan/tests/functional/api/test_follow.py b/ckan/tests/functional/api/test_follow.py
index eb4530e3cea..79e6f5dab17 100644
--- a/ckan/tests/functional/api/test_follow.py
+++ b/ckan/tests/functional/api/test_follow.py
@@ -25,6 +25,43 @@ def datetime_from_string(s):
'''
return datetime.datetime.strptime(s, '%Y-%m-%dT%H:%M:%S.%f')
+def follow(func):
+ '''Return a wrapper function for a follow_* function.
+
+ The wrapper functions test the `followee_list` and `followee_count` API
+ calls, in addition to any tests carried out by the wrapped function.
+
+ '''
+ def wrapped_func(app, follower_id, apikey, object_id, object_arg,
+ sysadmin_apikey):
+ followee_count_before = ckan.tests.call_action_api(app,
+ 'followee_count', id=follower_id)
+ followees_before = ckan.tests.call_action_api(app, 'followee_list',
+ id=follower_id, apikey=sysadmin_apikey)
+
+ func(app, follower_id, apikey, object_id, object_arg, sysadmin_apikey)
+
+ followee_count_after = ckan.tests.call_action_api(app,
+ 'followee_count', id=follower_id)
+ followees_after = ckan.tests.call_action_api(app, 'followee_list',
+ id=follower_id, apikey=sysadmin_apikey)
+
+ assert followee_count_after == followee_count_before + 1, (
+ "After a user follows an object, the user's `followee_count` "
+ "should increase by 1")
+
+ assert len(followees_after) == len(followees_before) + 1, (
+ "After a user follows an object, the object should appear in "
+ "the user's `followee_list`")
+ assert len([followee for followee in followees_after
+ if followee['dict']['id'] == object_id]) == 1, (
+ "After a user follows an object, the object should appear in "
+ "the user's `followee_list`")
+
+ return wrapped_func
+
+
+@follow
def follow_user(app, follower_id, apikey, object_id, object_arg,
sysadmin_apikey):
'''Test a user starting to follow another user via the API.
@@ -86,6 +123,8 @@ def follow_user(app, follower_id, apikey, object_id, object_arg,
'user_followee_count', id=follower_id)
assert followee_count_after == followee_count_before + 1
+
+@follow
def follow_dataset(app, follower_id, apikey, dataset_id, dataset_arg,
sysadmin_apikey):
'''Test a user starting to follow a dataset via the API.
@@ -147,6 +186,8 @@ def follow_dataset(app, follower_id, apikey, dataset_id, dataset_arg,
'dataset_followee_count', id=follower_id)
assert followee_count_after == followee_count_before + 1
+
+@follow
def follow_group(app, user_id, apikey, group_id, group_arg, sysadmin_apikey):
'''Test a user starting to follow a group via the API.
@@ -297,6 +338,20 @@ def test_00_sysadmin_can_get_group_follower_list(self):
ckan.tests.call_action_api(self.app, 'group_follower_list',
id='roger', status=200, apikey=self.testsysadmin['apikey'])
+ def test_00_visitor_cannot_get_followee_list(self):
+ ckan.tests.call_action_api(self.app, 'followee_list',
+ id=self.russianfan['id'], status=403)
+
+ def test_00_user_cannot_get_followee_list(self):
+ ckan.tests.call_action_api(self.app, 'followee_list',
+ id=self.russianfan['id'], status=403,
+ apikey=self.annafan['apikey'])
+
+ def test_00_sysadmin_can_get_followee_list(self):
+ ckan.tests.call_action_api(self.app, 'followee_list',
+ id=self.russianfan['id'], status=200,
+ apikey=self.testsysadmin['apikey'])
+
def test_00_visitor_cannot_get_user_followee_list(self):
'''A visitor cannot see what users a user is following.'''
ckan.tests.call_action_api(self.app, 'user_followee_list',
@@ -509,6 +564,57 @@ def test_04_group_follower_count_no_followers(self):
'group_follower_count', id=self.davids_group['id'])
assert follower_count == 0
+ def _followee_count_bad_id(self, action):
+ for object_id in ('bad id', ' ', 3, 35.7, 'xxx', ''):
+ error = ckan.tests.call_action_api(self.app, action,
+ status=409, id=object_id)
+ assert 'id' in error
+
+ def test_04_followee_count_bad_id(self):
+ self._followee_count_bad_id('followee_count')
+
+ def test_04_user_followee_count_bad_id(self):
+ self._followee_count_bad_id('user_followee_count')
+
+ def test_04_dataset_followee_count_bad_id(self):
+ self._followee_count_bad_id('dataset_followee_count')
+
+ def test_04_group_followee_count_bad_id(self):
+ self._followee_count_bad_id('group_followee_count')
+
+ def _followee_count_missing_id(self, action):
+ error = ckan.tests.call_action_api(self.app, action, status=409)
+ assert error['id'] == ['Missing value']
+
+ def test_04_followee_count_missing_id(self):
+ self._followee_count_missing_id('followee_count')
+
+ def test_04_user_followee_count_missing_id(self):
+ self._followee_count_missing_id('user_followee_count')
+
+ def test_04_dataset_followee_count_missing_id(self):
+ self._followee_count_missing_id('dataset_followee_count')
+
+ def test_04_group_followee_count_missing_id(self):
+ self._followee_count_missing_id('group_followee_count')
+
+ def _followee_count_not_following_anything(self, action):
+ followee_count = ckan.tests.call_action_api(self.app, action,
+ id=self.russianfan['id'])
+ assert followee_count == 0
+
+ def test_04_followee_count_not_following_anything(self):
+ self._followee_count_not_following_anything('followee_count')
+
+ def test_04_user_followee_count_not_following_anything(self):
+ self._followee_count_not_following_anything('user_followee_count')
+
+ def test_04_dataset_followee_count_not_following_anything(self):
+ self._followee_count_not_following_anything('dataset_followee_count')
+
+ def test_04_group_followee_count_not_following_anything(self):
+ self._followee_count_not_following_anything('group_followee_count')
+
def test_04_follower_list_bad_id(self):
for action in ('user_follower_list', 'dataset_follower_list',
'group_follower_list'):
@@ -541,6 +647,59 @@ def test_04_group_follower_list_no_followers(self):
id=self.davids_group['id'], apikey=self.testsysadmin['apikey'])
assert followers == []
+ def _followee_list_bad_id(self, action):
+ for object_id in ('bad id', ' ', 3, 35.7, 'xxx', ''):
+ error = ckan.tests.call_action_api(self.app, action,
+ status=409, id=object_id,
+ apikey=self.testsysadmin['apikey'])
+ assert error['id']
+
+ def test_04_followee_list_bad_id(self):
+ self._followee_list_bad_id('followee_list')
+
+ def test_04_user_followee_list_bad_id(self):
+ self._followee_list_bad_id('user_followee_list')
+
+ def test_04_dataset_followee_list_bad_id(self):
+ self._followee_list_bad_id('dataset_followee_list')
+
+ def test_04_group_followee_list_bad_id(self):
+ self._followee_list_bad_id('group_followee_list')
+
+ def _followee_list_missing_id(self, action):
+ error = ckan.tests.call_action_api(self.app, action, status=409,
+ apikey=self.testsysadmin['apikey'])
+ assert error['id'] == ['Missing value']
+
+ def test_04_followee_list_missing_id(self):
+ self._followee_list_missing_id('followee_list')
+
+ def test_04_user_followee_list_missing_id(self):
+ self._followee_list_missing_id('user_followee_list')
+
+ def test_04_dataset_followee_missing_bad_id(self):
+ self._followee_list_missing_id('dataset_followee_list')
+
+ def test_04_group_followee_missing_bad_id(self):
+ self._followee_list_missing_id('group_followee_list')
+
+ def _followee_list_not_following_anything(self, action):
+ followees = ckan.tests.call_action_api(self.app, action,
+ id=self.russianfan['id'], apikey=self.russianfan['apikey'])
+ assert followees == []
+
+ def test_04_followee_list_not_following_anything(self):
+ self._followee_list_not_following_anything('followee_list')
+
+ def test_04_user_followee_list_not_following_anything(self):
+ self._followee_list_not_following_anything('user_followee_list')
+
+ def test_04_dataset_followee_not_following_anything(self):
+ self._followee_list_not_following_anything('dataset_followee_list')
+
+ def test_04_group_followee_not_following_anything(self):
+ self._followee_list_not_following_anything('group_followee_list')
+
def test_04_am_following_bad_id(self):
for action in ('am_following_dataset', 'am_following_user',
'am_following_group'):
@@ -761,6 +920,10 @@ def _unfollow_user(self, follower_id, apikey, object_id, object_arg):
# Record the user's number of followers before.
count_before = ckan.tests.call_action_api(self.app,
'user_follower_count', id=object_id)
+ followee_count_before = ckan.tests.call_action_api(self.app,
+ 'followee_count', id=follower_id)
+ user_followee_count_before = ckan.tests.call_action_api(self.app,
+ 'user_followee_count', id=follower_id)
# Check that the user is following the object.
am_following = ckan.tests.call_action_api(self.app,
@@ -787,6 +950,25 @@ def _unfollow_user(self, follower_id, apikey, object_id, object_arg):
'user_follower_count', id=object_id)
assert count_after == count_before - 1
+ # Check that the user doesn't appear in the subject's list of
+ # followees.
+ followees = ckan.tests.call_action_api(self.app, 'followee_list',
+ id=follower_id, apikey=apikey)
+ assert len([followee for followee in followees
+ if followee['dict']['id'] == object_id]) == 0
+ followees = ckan.tests.call_action_api(self.app, 'user_followee_list',
+ id=follower_id, apikey=apikey)
+ assert len([followee for followee in followees
+ if followee['id'] == object_id]) == 0
+
+ # Check the the subject's followee cont has decreased by 1.
+ count_after = ckan.tests.call_action_api(self.app, 'followee_count',
+ id=follower_id)
+ assert count_after == followee_count_before - 1
+ count_after = ckan.tests.call_action_api(self.app,
+ 'user_followee_count', id=follower_id)
+ assert count_after == user_followee_count_before - 1
+
def _unfollow_dataset(self, user_id, apikey, dataset_id, dataset_arg):
'''Test a user unfollowing a dataset via the API.
@@ -800,6 +982,10 @@ def _unfollow_dataset(self, user_id, apikey, dataset_id, dataset_arg):
# Record the dataset's number of followers before.
count_before = ckan.tests.call_action_api(self.app,
'dataset_follower_count', id=dataset_id)
+ followee_count_before = ckan.tests.call_action_api(self.app,
+ 'followee_count', id=user_id)
+ dataset_followee_count_before = ckan.tests.call_action_api(self.app,
+ 'dataset_followee_count', id=user_id)
# Check that the user is following the dataset.
am_following = ckan.tests.call_action_api(self.app,
@@ -828,6 +1014,25 @@ def _unfollow_dataset(self, user_id, apikey, dataset_id, dataset_arg):
'dataset_follower_count', id=dataset_id)
assert count_after == count_before - 1
+ # Check that the dataset doesn't appear in the user's list of
+ # followees.
+ followees = ckan.tests.call_action_api(self.app, 'followee_list',
+ id=user_id, apikey=apikey)
+ assert len([followee for followee in followees
+ if followee['dict']['id'] == dataset_id]) == 0
+ followees = ckan.tests.call_action_api(self.app,
+ 'dataset_followee_list', id=user_id, apikey=apikey)
+ assert len([followee for followee in followees
+ if followee['id'] == dataset_id]) == 0
+
+ # Check the the user's followee count has decreased by 1.
+ count_after = ckan.tests.call_action_api(self.app, 'followee_count',
+ id=user_id)
+ assert count_after == followee_count_before - 1
+ count_after = ckan.tests.call_action_api(self.app,
+ 'dataset_followee_count', id=user_id)
+ assert count_after == dataset_followee_count_before - 1
+
def _unfollow_group(self, user_id, apikey, group_id, group_arg):
'''Test a user unfollowing a group via the API.
@@ -841,6 +1046,10 @@ def _unfollow_group(self, user_id, apikey, group_id, group_arg):
# Record the group's number of followers before.
count_before = ckan.tests.call_action_api(self.app,
'group_follower_count', id=group_id)
+ followee_count_before = ckan.tests.call_action_api(self.app,
+ 'followee_count', id=user_id)
+ group_followee_count_before = ckan.tests.call_action_api(self.app,
+ 'group_followee_count', id=user_id)
# Check that the user is following the group.
am_following = ckan.tests.call_action_api(self.app,
@@ -868,6 +1077,26 @@ def _unfollow_group(self, user_id, apikey, group_id, group_arg):
'group_follower_count', id=group_id)
assert count_after == count_before - 1
+ # Check that the group doesn't appear in the user's list of
+ # followees.
+ followees = ckan.tests.call_action_api(self.app, 'followee_list',
+ id=user_id, apikey=apikey)
+ assert len([followee for followee in followees
+ if followee['dict']['id'] == group_id]) == 0
+ followees = ckan.tests.call_action_api(self.app,
+ 'group_followee_list', id=user_id,
+ apikey=self.testsysadmin['apikey'])
+ assert len([followee for followee in followees
+ if followee['id'] == group_id]) == 0
+
+ # Check the the user's followee count has decreased by 1.
+ count_after = ckan.tests.call_action_api(self.app, 'followee_count',
+ id=user_id)
+ assert count_after == followee_count_before - 1
+ count_after = ckan.tests.call_action_api(self.app,
+ 'group_followee_count', id=user_id)
+ assert count_after == group_followee_count_before - 1
+
def test_02_follower_delete_by_id(self):
self._unfollow_user(self.annafan['id'], self.annafan['apikey'],
self.joeadmin['id'], self.joeadmin['id'])
@@ -984,6 +1213,13 @@ def test_01_on_delete_cascade_api(self):
status=409, id='joeadmin', apikey=self.testsysadmin['apikey'])
assert 'id' in error
+ # It should no longer be possible to get joeadmin's followee lists.
+ for action in ('followee_list', 'user_followee_list',
+ 'dataset_followee_list', 'group_followee_list'):
+ error = ckan.tests.call_action_api(self.app, action, status=409,
+ id='joeadmin', apikey=self.testsysadmin['apikey'])
+ assert 'id' in error
+
# It should no longer be possible to get warandpeace's follower list.
error = ckan.tests.call_action_api(self.app, 'dataset_follower_list',
status=409, id='warandpeace', apikey=self.testsysadmin['apikey'])
@@ -999,6 +1235,13 @@ def test_01_on_delete_cascade_api(self):
status=409, id='joeadmin')
assert 'id' in error
+ # It should no longer be possible to get joeadmin's followee counts.
+ for action in ('followee_count', 'user_followee_count',
+ 'dataset_followee_count', 'group_followee_count'):
+ error = ckan.tests.call_action_api(self.app, action, status=409,
+ id='joeadmin')
+ assert 'id' in error
+
# It should no longer be possible to get warandpeace's follower count.
error = ckan.tests.call_action_api(self.app, 'dataset_follower_count',
status=409, id='warandpeace')
diff --git a/ckan/tests/lib/test_dictization.py b/ckan/tests/lib/test_dictization.py
index 61828f0a27d..f868948a59b 100644
--- a/ckan/tests/lib/test_dictization.py
+++ b/ckan/tests/lib/test_dictization.py
@@ -124,7 +124,10 @@ def setup_class(cls):
'title': u'A Novel By Tolstoy',
'tracking_summary': {'total': 0, 'recent': 0},
'url': u'http://www.annakarenina.com',
- 'version': u'0.7a'}
+ 'version': u'0.7a',
+ 'num_tags': 3,
+ 'num_resources': 2,
+ }
@classmethod
@@ -256,6 +259,8 @@ def test_03_package_to_api1(self):
asdict = pkg.as_dict()
asdict['download_url'] = asdict['resources'][0]['url']
asdict['license_title'] = u'Other (Open)'
+ asdict['num_tags'] = 3
+ asdict['num_resources'] = 2
dictize = package_to_api1(pkg, context)
# the is_dict method doesn't care about organizations
@@ -274,6 +279,8 @@ def test_04_package_to_api1_with_relationship(self):
as_dict = pkg.as_dict()
as_dict['license_title'] = None
+ as_dict['num_tags'] = 0
+ as_dict['num_resources'] = 0
dictize = package_to_api1(pkg, context)
as_dict["relationships"].sort(key=lambda x:x.items())
@@ -314,6 +321,8 @@ def test_06_package_to_api2_with_relationship(self):
as_dict = pkg.as_dict(ref_package_by='id', ref_group_by='id')
as_dict['license_title'] = None
+ as_dict['num_tags'] = 0
+ as_dict['num_resources'] = 0
dictize = package_to_api2(pkg, context)
as_dict["relationships"].sort(key=lambda x:x.items())
@@ -739,12 +748,15 @@ def test_13_get_package_in_past(self):
u'url': u'http://newurl',
u'webstore_last_updated': None,
u'webstore_url': None})
+ third_dictized['num_resources'] = third_dictized['num_resources'] + 1
third_dictized['tags'].insert(1, {'name': u'newnew_tag', 'display_name': u'newnew_tag', 'state': 'active'})
+ third_dictized['num_tags'] = third_dictized['num_tags'] + 1
third_dictized['extras'].insert(0, {'key': 'david',
'value': u'"new_value"',
'state': u'active'})
third_dictized['state'] = 'active'
+ third_dictized['state'] = 'active'
pprint(third_dictized)
pprint(forth_dictized)