From fa3bfc719e9eacf5d2534f5a57becc8b68093b65 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Mon, 10 Dec 2012 17:22:42 +0100 Subject: [PATCH 01/61] [#3028] Add `followee_list` and `followee_count` action functions --- ckan/logic/action/get.py | 54 ++++++++++++++++++++++++ ckan/tests/functional/api/test_follow.py | 41 ++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/ckan/logic/action/get.py b/ckan/logic/action/get.py index c85e3a7ece9..d56ddd32598 100644 --- a/ckan/logic/action/get.py +++ b/ckan/logic/action/get.py @@ -2041,6 +2041,23 @@ def _followee_count(context, data_dict, FollowerClass): return FollowerClass.followee_count(data_dict['id']) +def followee_count(context, data_dict): + '''Return the number of users that are followed by the given user. + + :param id: the id of the user + :type id: string + + :rtype: int + + ''' + model = context['model'] + return sum(( + _followee_count(context, data_dict, model.UserFollowingUser), + _followee_count(context, data_dict, model.UserFollowingDataset), + _followee_count(context, data_dict, model.UserFollowingGroup), + )) + + def user_followee_count(context, data_dict): '''Return the number of users that are followed by the given user. @@ -2080,6 +2097,43 @@ def group_followee_count(context, data_dict): context['model'].UserFollowingGroup) +def _followee_key_function(followee): + '''A key function used to sort a list of followee dicts.''' + display_name = followee.get('display_name') + fullname = followee.get('fullname') + title = followee.get('title') + name = followee.get('name') + return display_name or fullname or title or name + + +def followee_list(context, data_dict): + '''Return the list of objects that are followed by the given user. + + Returns all objects, of any type, that the given user is following + (e.g. followed users, followed datasets, followed groups). + + :param id: the id of the user + :type id: string + + :rtype: list of dictionaries + + ''' + # This function doesn't do its own authorization or validation because + # it's just a wrapper for the *_followee_list() functions that each do + # their own. + + # Get the followed objects. + # TODO: Catch exceptions raised by these *_followee_list() functions? + followee_dicts = [] + for followee_list_function in (user_followee_list, dataset_followee_list, + group_followee_list): + followee_dicts.extend(followee_list_function(context, data_dict)) + + followee_dicts.sort(key=_followee_key_function) + + return followee_dicts + + def user_followee_list(context, data_dict): '''Return the list of users that are followed by the given user. diff --git a/ckan/tests/functional/api/test_follow.py b/ckan/tests/functional/api/test_follow.py index 90bc4a23101..e12d2f8e938 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): + 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) + + func(app, follower_id, apikey, object_id, object_arg) + + 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) + + 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['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): '''Test a user starting to follow another user via the API. @@ -85,6 +122,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): '''Test a user starting to follow a dataset via the API. @@ -145,6 +184,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): '''Test a user starting to follow a group via the API. From b75d0463b0d1cb8c4aa39f497b484357d9f1a3e3 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Mon, 10 Dec 2012 17:38:11 +0100 Subject: [PATCH 02/61] [#3028] Add search filtering to followee_list --- ckan/logic/action/get.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ckan/logic/action/get.py b/ckan/logic/action/get.py index d56ddd32598..fb87a6457a0 100644 --- a/ckan/logic/action/get.py +++ b/ckan/logic/action/get.py @@ -2131,6 +2131,17 @@ def followee_list(context, data_dict): followee_dicts.sort(key=_followee_key_function) + q = data_dict.get('q') + if q: + q = q.strip().lower() + matching_followee_dicts = [] + for followee_dict in followee_dicts: + display_name = _followee_key_function(followee_dict) + display_name = display_name.strip().lower() + if display_name.startswith(q): + matching_followee_dicts.append(followee_dict) + followee_dicts = matching_followee_dicts + return followee_dicts From 965640c41712b60e637196f856eb0d603b778ab4 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Mon, 10 Dec 2012 18:07:11 +0100 Subject: [PATCH 03/61] [#3028] Change format of dicts returned by followee_list Match the format requested by @johnmartin for the frontend --- ckan/logic/action/get.py | 41 +++++++++++++++--------- ckan/tests/functional/api/test_follow.py | 2 +- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/ckan/logic/action/get.py b/ckan/logic/action/get.py index fb87a6457a0..f5f45a1665d 100644 --- a/ckan/logic/action/get.py +++ b/ckan/logic/action/get.py @@ -2097,15 +2097,6 @@ def group_followee_count(context, data_dict): context['model'].UserFollowingGroup) -def _followee_key_function(followee): - '''A key function used to sort a list of followee dicts.''' - display_name = followee.get('display_name') - fullname = followee.get('fullname') - title = followee.get('title') - name = followee.get('name') - return display_name or fullname or title or name - - def followee_list(context, data_dict): '''Return the list of objects that are followed by the given user. @@ -2115,9 +2106,22 @@ def followee_list(context, data_dict): :param id: the id of the user :type id: string - :rtype: list of dictionaries + :rtype: list of dictionaries, each with keys 'type' (e.g. 'user', + 'dataset' or 'group'), 'display_name' (e.g. a user's display name, + or a package's title) and 'dict' (e.g. a dict representing the + followed user, package or group, the same as the dict that would be + returned by user_show, package_show or group_show) ''' + + def display_name(followee): + '''Return a display name for the given user, group or dataset dict.''' + display_name = followee.get('display_name') + fullname = followee.get('fullname') + title = followee.get('title') + name = followee.get('name') + return display_name or fullname or title or name + # This function doesn't do its own authorization or validation because # it's just a wrapper for the *_followee_list() functions that each do # their own. @@ -2125,11 +2129,18 @@ def followee_list(context, data_dict): # Get the followed objects. # TODO: Catch exceptions raised by these *_followee_list() functions? followee_dicts = [] - for followee_list_function in (user_followee_list, dataset_followee_list, - group_followee_list): - followee_dicts.extend(followee_list_function(context, data_dict)) - - followee_dicts.sort(key=_followee_key_function) + for followee_list_function, followee_type in ( + (user_followee_list, 'user'), + (dataset_followee_list, 'dataset'), + (group_followee_list, 'group')): + dicts = followee_list_function(context, data_dict) + for d in dicts: + followee_dicts.append( + {'type': followee_type, + 'display_name': display_name(d), + 'dict': d}) + + followee_dicts.sort(key=lambda d: d['display_name']) q = data_dict.get('q') if q: diff --git a/ckan/tests/functional/api/test_follow.py b/ckan/tests/functional/api/test_follow.py index e12d2f8e938..98503a84b0a 100644 --- a/ckan/tests/functional/api/test_follow.py +++ b/ckan/tests/functional/api/test_follow.py @@ -54,7 +54,7 @@ def wrapped_func(app, follower_id, apikey, object_id, object_arg): "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['id'] == object_id]) == 1, ( + if followee['dict']['id'] == object_id]) == 1, ( "After a user follows an object, the object should appear in " "the user's `followee_list`") From c9039b65b8dc4b90b775c67bf9160fbc8cc115f8 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Mon, 10 Dec 2012 18:43:29 +0100 Subject: [PATCH 04/61] [#3028] Refactor a couple of activity streams methods Refactor _activites_from_users_followed_by_user_query() and _activites_from_datsets_followed_by_user_query(), make them use an SQLAlchemy union of _user_activity_query() for all users followed by the given user and _package_activity_query() for all datasets followed by the given user, respectively. This just ensures that what appears in a user's public activity stream, for example, is exactly what will appear in your dashboard activity stream if you follow that user. Better than having two different implementations of the SQL for this query. _activites_from_groups_followed_by_user_query() already works this way. --- ckan/model/activity.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/ckan/model/activity.py b/ckan/model/activity.py index cddf9e04e01..7e329fbb263 100644 --- a/ckan/model/activity.py +++ b/ckan/model/activity.py @@ -183,20 +183,32 @@ def group_activity_list(group_id, limit=15): def _activites_from_users_followed_by_user_query(user_id): '''Return a query for all activities from users that user_id follows.''' import ckan.model as model - q = model.Session.query(model.Activity) - q = q.join(model.UserFollowingUser, - model.UserFollowingUser.object_id == model.Activity.user_id) - q = q.filter(model.UserFollowingUser.follower_id == user_id) + + # Get a list of the users that the given user is following. + follower_objects = model.UserFollowingUser.followee_list(user_id) + if not follower_objects: + # Return a query with no results. + return model.Session.query(model.Activity).filter("0=1") + + q = _user_activity_query(follower_objects[0].object_id) + q = q.union_all(*[_user_activity_query(follower.object_id) + for follower in follower_objects[1:]]) return q def _activities_from_datasets_followed_by_user_query(user_id): '''Return a query for all activities from datasets that user_id follows.''' import ckan.model as model - q = model.Session.query(model.Activity) - q = q.join(model.UserFollowingDataset, - model.UserFollowingDataset.object_id == model.Activity.object_id) - q = q.filter(model.UserFollowingDataset.follower_id == user_id) + + # Get a list of the datasets that the user is following. + follower_objects = model.UserFollowingDataset.followee_list(user_id) + if not follower_objects: + # Return a query with no results. + return model.Session.query(model.Activity).filter("0=1") + + q = _package_activity_query(follower_objects[0].object_id) + q = q.union_all(*[_package_activity_query(follower.object_id) + for follower in follower_objects[1:]]) return q From 06a51618cdeba1a488b9c48f6cf469ac4859fe23 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Mon, 10 Dec 2012 18:51:02 +0100 Subject: [PATCH 05/61] [#3028] Fix a mistake in followee_list --- ckan/logic/action/get.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ckan/logic/action/get.py b/ckan/logic/action/get.py index f5f45a1665d..19ae4da6877 100644 --- a/ckan/logic/action/get.py +++ b/ckan/logic/action/get.py @@ -2147,9 +2147,7 @@ def display_name(followee): q = q.strip().lower() matching_followee_dicts = [] for followee_dict in followee_dicts: - display_name = _followee_key_function(followee_dict) - display_name = display_name.strip().lower() - if display_name.startswith(q): + if followee_dict['display_name'].strip().lower().startswith(q): matching_followee_dicts.append(followee_dict) followee_dicts = matching_followee_dicts From 6f277a796d5a948a7f4a116f84d98ccf986f6545 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Mon, 10 Dec 2012 18:52:57 +0100 Subject: [PATCH 06/61] [#3028] Document an undocumented param --- ckan/logic/action/get.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ckan/logic/action/get.py b/ckan/logic/action/get.py index 19ae4da6877..c8e69a1cdc2 100644 --- a/ckan/logic/action/get.py +++ b/ckan/logic/action/get.py @@ -2106,6 +2106,10 @@ def followee_list(context, data_dict): :param id: the id of the user :type id: string + :param q: a query string to limit results by, only objects whose display + name begins with the given string (case-insensitive) wil be returned + :type q: string + :rtype: list of dictionaries, each with keys 'type' (e.g. 'user', 'dataset' or 'group'), 'display_name' (e.g. a user's display name, or a package's title) and 'dict' (e.g. a dict representing the From 0cc2dfa033460f6d744af35a6c88a57b8d3c735a Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Mon, 10 Dec 2012 18:54:47 +0100 Subject: [PATCH 07/61] [#3028] Update a comment --- ckan/logic/action/get.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ckan/logic/action/get.py b/ckan/logic/action/get.py index c8e69a1cdc2..5e80d8d61ef 100644 --- a/ckan/logic/action/get.py +++ b/ckan/logic/action/get.py @@ -2117,6 +2117,7 @@ def followee_list(context, data_dict): returned by user_show, package_show or group_show) ''' + # TODO: Validation, authorization. def display_name(followee): '''Return a display name for the given user, group or dataset dict.''' @@ -2126,10 +2127,6 @@ def display_name(followee): name = followee.get('name') return display_name or fullname or title or name - # This function doesn't do its own authorization or validation because - # it's just a wrapper for the *_followee_list() functions that each do - # their own. - # Get the followed objects. # TODO: Catch exceptions raised by these *_followee_list() functions? followee_dicts = [] From e5dcffed03734cee091363dcaf4b39584b3a8cd0 Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Mon, 10 Dec 2012 19:02:05 +0100 Subject: [PATCH 08/61] [#3028] Fix a docstring --- ckan/logic/action/get.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ckan/logic/action/get.py b/ckan/logic/action/get.py index 5e80d8d61ef..eb1d91bd22c 100644 --- a/ckan/logic/action/get.py +++ b/ckan/logic/action/get.py @@ -2042,7 +2042,10 @@ def _followee_count(context, data_dict, FollowerClass): def followee_count(context, data_dict): - '''Return the number of users that are followed by the given user. + '''Return the number of objects that are followed by the given user. + + Counts all objects, of any type, that the given user is following + (e.g. followed users, followed datasets, followed groups). :param id: the id of the user :type id: string From 080cef490e5678904f16f35d8dd63ef10366dcb3 Mon Sep 17 00:00:00 2001 From: John Martin Date: Wed, 12 Dec 2012 16:08:01 +0000 Subject: [PATCH 09/61] [#3028] Dashboard now shows dropdown for filtering of activity stream Still todo: * Add header for context of specific item active within dropdown * Snippetize the followee list * Searching the dropdown (with JS enabled) --- ckan/controllers/user.py | 65 +++++++++++- ckan/lib/helpers.py | 12 ++- .../base/images/full-width-nav-right.png | Bin 0 -> 166 bytes .../base/javascript/modules/dashboard.js | 40 +++++-- ckan/public/base/less/dashboard.less | 80 +++++++++++++- .../activity_stream_items.html | 14 ++- ckan/templates/user/dashboard.html | 99 +++++++++++++----- 7 files changed, 263 insertions(+), 47 deletions(-) create mode 100644 ckan/public/base/images/full-width-nav-right.png diff --git a/ckan/controllers/user.py b/ckan/controllers/user.py index e32d32587cc..77eaee391c8 100644 --- a/ckan/controllers/user.py +++ b/ckan/controllers/user.py @@ -495,13 +495,76 @@ def activity(self, id): return render('user/activity_stream.html') + def _get_dashboard_context(self, filter_type=None, filter_id=None, q=None): + + def display_name(followee): + '''Return a display name for the given user, group or dataset dict.''' + display_name = followee.get('display_name') + fullname = followee.get('fullname') + title = followee.get('title') + name = followee.get('name') + return display_name or fullname or title or name + + if (filter_type and filter_id): + context = {'model': model, 'session': model.Session, + 'user': c.user or c.author, 'for_view': True} + data_dict = {'id': filter_id} + followee = None + # Is this a valid type? + if filter_type == 'dataset': + try: + followee = get_action('package_show')(context, data_dict) + except NotFound: + abort(404, _('Dataset not found')) + except NotAuthorized: + abort(401, _('Unauthorized to read package %s') % id) + elif filter_type == 'user': + try: + followee = get_action('user_show')(context, data_dict) + except NotFound: + abort(404, _('User not found')) + except NotAuthorized: + abort(401, _('Unauthorized to read user %s') % id) + elif filter_type == 'group': + try: + followee = get_action('group_show')(context, data_dict) + except NotFound: + abort(404, _('Group not found')) + except NotAuthorized: + abort(401, _('Unauthorized to read group %s') % id) + else: + raise abort(404, _('Follow item not found')) + + if not followee == None: + return { + 'filter_type': filter_type, + 'q': q, + 'context': display_name(followee), + 'selected_id': followee.get('id'), + 'dict': followee, + } + + return { + 'filter_type': filter_type, + 'q': q, + 'context': _('Everything'), + 'selected_id': False, + 'dict': None, + } + def dashboard(self, id=None): context = {'model': model, 'session': model.Session, 'user': c.user or c.author, 'for_view': True} data_dict = {'id': id, 'user_obj': c.userobj} self._setup_template_variables(context, data_dict) - c.dashboard_activity_stream = h.dashboard_activity_stream(id) + q = request.params.get('q', u'') + filter_type = request.params.get('type', u'') + filter_id = request.params.get('name', u'') + + c.followee_list = get_action('followee_list')(context, {'id': c.userobj.id, 'q': q}) + c.dashboard_activity_stream_context = self._get_dashboard_context(filter_type, filter_id, q) + c.dashboard_activity_stream = h.dashboard_activity_stream(id, filter_type, filter_id) # Mark the user's new activities as old whenever they view their # dashboard page. diff --git a/ckan/lib/helpers.py b/ckan/lib/helpers.py index af0bb8199e9..f0fbe33264a 100644 --- a/ckan/lib/helpers.py +++ b/ckan/lib/helpers.py @@ -1174,7 +1174,7 @@ def groups_available(): return logic.get_action('group_list_authz')(context, data_dict) -def dashboard_activity_stream(user_id): +def dashboard_activity_stream(user_id, filter_type=None, filter_id=None): '''Return the dashboard activity stream of the given user. :param user_id: the id of the user @@ -1186,8 +1186,14 @@ def dashboard_activity_stream(user_id): ''' import ckan.logic as logic context = {'model': model, 'session': model.Session, 'user': c.user} - return logic.get_action('dashboard_activity_list_html')(context, - {'id': user_id}) + if filter_type == 'user': + return logic.get_action('user_activity_list_html')(context, {'id': filter_id}) + elif filter_type == 'dataset': + return logic.get_action('package_activity_list_html')(context, {'id': filter_id}) + elif filter_type == 'group': + return logic.get_action('group_activity_list_html')(context, {'id': filter_id}) + else: + return logic.get_action('dashboard_activity_list_html')(context, {'id': user_id}) def escape_js(str_to_escape): diff --git a/ckan/public/base/images/full-width-nav-right.png b/ckan/public/base/images/full-width-nav-right.png new file mode 100644 index 0000000000000000000000000000000000000000..344b92cb458cc5b14b3332ff8b7dcb2b9b3f4284 GIT binary patch literal 166 zcmeAS@N?(olHy`uVBq!ia0y~yVB`d{89A7NsZ)P{3kcg*?V@SoVq=b~jgrw9oX;ubCHU`zLY)azgT -{% for activity in activities %} - {% snippet 'snippets/activity_item.html', activity=activity %} -{% endfor %} - +{% if activities %} +
    + {% for activity in activities %} + {% snippet 'snippets/activity_item.html', activity=activity %} + {% endfor %} +
+{% else %} +

{{ _('No activities are within this activity stream') }}

+{% endif %} diff --git a/ckan/templates/user/dashboard.html b/ckan/templates/user/dashboard.html index 2beaaaddea9..061326be74b 100644 --- a/ckan/templates/user/dashboard.html +++ b/ckan/templates/user/dashboard.html @@ -1,44 +1,91 @@ {% extends "page.html" %} +{% macro followee_icon(type) -%} + {% if type == 'dataset' %} + + {% elif type == 'user' %} + + {% elif type == 'group' %} + + {% endif %} +{%- endmacro %} + {% set user = c.user_dict %} {% block subtitle %}{{ _('Dashboard') }}{% endblock %} {% block breadcrumb_content %} -
  • {{ _('Dashboard') }}
  • +
  • {{ _('Dashboard') }}
  • {% endblock %} {% block actions_content %} {% if h.check_access('package_create') %} -
  • {% link_for _('Add Dataset'), controller='package', action='new', class_="btn btn-primary icon-large", icon="plus" %}
  • +
  • {% link_for _('Add a dataset'), controller='package', action='new', class_="btn btn-primary icon-large", icon="plus" %}
  • {% endif %} {% endblock %} -{% block primary_content %} -
    -
    -

    {{ _('News feed') }} {{ _('Activity from users and datasets that you follow') }}

    - {{ c.dashboard_activity_stream }} +{% block primary %} +
    +
    +
    +
    + + {{ _('Show me:') }} + {{ c.dashboard_activity_stream_context.context }} + + + +
    +

    + {{ _('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 %} From 6ef49b705c4aa171be84772727a6c0417946dbb9 Mon Sep 17 00:00:00 2001 From: John Martin Date: Wed, 12 Dec 2012 16:52:34 +0000 Subject: [PATCH 10/61] [#3028] Snippetized followee dropdown and added context to dashboard when filtering --- ckan/public/base/less/dashboard.less | 23 +++++++++ .../popover-context-dataset.html | 6 +++ .../ajax_snippets/popover-context-group.html | 6 +++ .../ajax_snippets/popover-context-user.html | 10 +++- ckan/templates/snippets/popover_context.html | 10 ++++ ckan/templates/user/dashboard.html | 51 +++---------------- .../user/snippets/followee_dropdown.html | 45 ++++++++++++++++ 7 files changed, 105 insertions(+), 46 deletions(-) create mode 100644 ckan/templates/snippets/popover_context.html create mode 100644 ckan/templates/user/snippets/followee_dropdown.html diff --git a/ckan/public/base/less/dashboard.less b/ckan/public/base/less/dashboard.less index 478b445c285..cb44cbd5251 100644 --- a/ckan/public/base/less/dashboard.less +++ b/ckan/public/base/less/dashboard.less @@ -15,6 +15,29 @@ } } +#followee-filter { + .btn { + font-weight: normal; + strong { + max-width: 50px; + overflow: hidden; + text-overflow: ellipsis; + } + } +} + +.dashboard-aside-context { + margin: -20px -25px 20px -20px; + padding: 20px; + border-left: 1px solid #DCDCDC; + border-bottom: 1px solid #DCDCDC; + background-color: @moduleHeadingBackgroundColorStart; + .border-radius(0 5px 0 0); + h2 { + margin-bottom: 10px; + } +} + .popover-followee { .popover-title { display: none; diff --git a/ckan/templates/ajax_snippets/popover-context-dataset.html b/ckan/templates/ajax_snippets/popover-context-dataset.html index 09732dd5d09..b8704d59185 100644 --- a/ckan/templates/ajax_snippets/popover-context-dataset.html +++ b/ckan/templates/ajax_snippets/popover-context-dataset.html @@ -11,14 +11,20 @@ View Dataset
    + {% if num_resources or num_tags %}
    + {% if num_resources %}
    {{ _('Resources') }}
    {{ num_resources }}
    + {% endif %} + {% if num_tags %}
    {{ _('Tags') }}
    {{ num_tags }}
    + {% endif %}
    + {% endif %} diff --git a/ckan/templates/ajax_snippets/popover-context-group.html b/ckan/templates/ajax_snippets/popover-context-group.html index b0efe47f502..318ca662afc 100644 --- a/ckan/templates/ajax_snippets/popover-context-group.html +++ b/ckan/templates/ajax_snippets/popover-context-group.html @@ -11,14 +11,20 @@ View Group + {% if num_followers or num_datasets %}
    + {% if num_followers %}
    {{ _('Followers') }}
    {{ num_followers }}
    + {% endif %} + {% if num_datasets %}
    {{ _('Datasets') }}
    {{ num_datasets }}
    + {% endif %}
    + {% endif %} diff --git a/ckan/templates/ajax_snippets/popover-context-user.html b/ckan/templates/ajax_snippets/popover-context-user.html index 5e7fbef5b62..6416dacc751 100644 --- a/ckan/templates/ajax_snippets/popover-context-user.html +++ b/ckan/templates/ajax_snippets/popover-context-user.html @@ -12,19 +12,27 @@ View Profile - + + {% if num_followers or number_administered_packages or number_of_edits %}
    + {% if num_followers %}
    {{ _('Followers') }}
    {{ num_followers }}
    + {% endif %} + {% if number_administered_packages %}
    {{ _('Datasets') }}
    {{ number_administered_packages }}
    + {% endif %} + {% if number_of_edits %}
    {{ _('Edits') }}
    {{ number_of_edits }}
    + {% endif %}
    + {% endif %} diff --git a/ckan/templates/snippets/popover_context.html b/ckan/templates/snippets/popover_context.html new file mode 100644 index 00000000000..0d9c46172a3 --- /dev/null +++ b/ckan/templates/snippets/popover_context.html @@ -0,0 +1,10 @@ +{%if type == 'user' %} +

    {{ dict.name }}

    + {% snippet 'ajax_snippets/popover-context-user.html', id=dict.id, name=dict.name, about=dict.about, is_me='false', num_followers=dict.num_followers, number_administered_packages=dict.number_administered_packages, number_of_edits=dict.number_of_edits %} +{%elif type == 'dataset' %} +

    {{ dict.title }}

    + {% snippet 'ajax_snippets/popover-context-dataset.html', id=dict.id, name=dict.name, notes=dict.notes, num_resources=dict.num_resources, num_tags=dict.num_tags %} +{%elif type == 'group' %} +

    {{ dict.title }}

    + {% snippet 'ajax_snippets/popover-context-group.html', id=dict.id, name=dict.name, description=dict.description, num_followers=dict.num_followers, num_datasets=dict.num_datasets %} +{% endif %} diff --git a/ckan/templates/user/dashboard.html b/ckan/templates/user/dashboard.html index 061326be74b..9aea7519999 100644 --- a/ckan/templates/user/dashboard.html +++ b/ckan/templates/user/dashboard.html @@ -1,15 +1,5 @@ {% extends "page.html" %} -{% macro followee_icon(type) -%} - {% if type == 'dataset' %} - - {% elif type == 'user' %} - - {% elif type == 'group' %} - - {% endif %} -{%- endmacro %} - {% set user = c.user_dict %} {% block subtitle %}{{ _('Dashboard') }}{% endblock %} @@ -28,41 +18,7 @@
    -
    - - {{ _('Show me:') }} - {{ c.dashboard_activity_stream_context.context }} - - - -
    + {% snippet 'user/snippets/followee_dropdown.html', context=c.dashboard_activity_stream_context, followees=c.followee_list %}

    {{ _('News feed') }} {{ _('Activity from items that you follow') }} @@ -70,6 +26,11 @@

    {{ c.dashboard_activity_stream }}

    {% if followees %}