Skip to content

Commit c3610ae

Browse files
committed
Paginate activity list for datasets
* Drop unused files * Allow filter activities by type
1 parent 65af260 commit c3610ae

File tree

8 files changed

+162
-143
lines changed

8 files changed

+162
-143
lines changed

ckan/logic/action/get.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2542,10 +2542,12 @@ def package_activity_list(context, data_dict):
25422542

25432543
offset = int(data_dict.get('offset', 0))
25442544
limit = data_dict['limit'] # defaulted, limited & made an int by schema
2545+
activity_type = data_dict.get('activity_type')
25452546

25462547
activity_objects = model.activity.package_activity_list(
25472548
package.id, limit=limit, offset=offset,
25482549
include_hidden_activity=include_hidden_activity,
2550+
activity_type=activity_type,
25492551
)
25502552

25512553
return model_dictize.activity_list_dictize(

ckan/logic/schema.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -611,7 +611,8 @@ def default_dashboard_activity_list_schema(
611611
def default_activity_list_schema(
612612
not_missing, unicode_safe, configured_default,
613613
natural_number_validator, limit_to_configured_maximum,
614-
ignore_missing, boolean_validator, ignore_not_sysadmin):
614+
ignore_missing, boolean_validator, ignore_not_sysadmin,
615+
activity_type_exists):
615616
schema = default_pagination_schema()
616617
schema['id'] = [not_missing, unicode_safe]
617618
schema['limit'] = [
@@ -620,6 +621,9 @@ def default_activity_list_schema(
620621
limit_to_configured_maximum('ckan.activity_list_limit_max', 100)]
621622
schema['include_hidden_activity'] = [
622623
ignore_missing, ignore_not_sysadmin, boolean_validator]
624+
schema['activity_type'] = [
625+
ignore_missing, activity_type_exists
626+
]
623627
return schema
624628

625629

ckan/logic/validators.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -297,24 +297,36 @@ def activity_type_exists(activity_type):
297297
raise Invalid('%s: %s' % (_('Not found'), _('Activity type')))
298298

299299

300-
# A dictionary mapping activity_type values from activity dicts to functions
301-
# for validating the object_id values from those same activity dicts.
302-
object_id_validators = {
300+
VALIDATORS_PACKAGE_ACTIVITY_TYPES = {
303301
'new package' : package_id_exists,
304302
'changed package' : package_id_exists,
305303
'deleted package' : package_id_exists,
306304
'follow dataset' : package_id_exists,
305+
}
306+
307+
VALIDATORS_USER_ACTIVITY_TYPES = {
307308
'new user' : user_id_exists,
308309
'changed user' : user_id_exists,
309310
'follow user' : user_id_exists,
311+
}
312+
313+
VALIDATORS_GROUP_ACTIVITY_TYPES = {
310314
'new group' : group_id_exists,
311315
'changed group' : group_id_exists,
312316
'deleted group' : group_id_exists,
313317
'new organization' : group_id_exists,
314318
'changed organization' : group_id_exists,
315319
'deleted organization' : group_id_exists,
316320
'follow group' : group_id_exists,
317-
}
321+
}
322+
323+
# A dictionary mapping activity_type values from activity dicts to functions
324+
# for validating the object_id values from those same activity dicts.
325+
object_id_validators = {
326+
**VALIDATORS_PACKAGE_ACTIVITY_TYPES,
327+
**VALIDATORS_USER_ACTIVITY_TYPES,
328+
**VALIDATORS_GROUP_ACTIVITY_TYPES,
329+
}
318330

319331
def object_id_validator(key, activity_dict, errors, context):
320332
'''Validate the 'object_id' value of an activity_dict.

ckan/model/activity.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,10 @@ def _package_activity_query(package_id):
182182

183183

184184
def package_activity_list(
185-
package_id, limit, offset, include_hidden_activity=False):
185+
package_id, limit, offset,
186+
include_hidden_activity=False,
187+
activity_type=None,
188+
):
186189
'''Return the given dataset (package)'s public activity stream.
187190
188191
Returns all activities about the given dataset, i.e. where the given
@@ -198,6 +201,9 @@ def package_activity_list(
198201
if not include_hidden_activity:
199202
q = _filter_activitites_from_users(q)
200203

204+
if activity_type:
205+
q = _filter_activitites_by_type(q, activity_type)
206+
201207
return _activities_at_offset(q, limit, offset)
202208

203209

@@ -450,9 +456,21 @@ def recently_changed_packages_activity_list(limit, offset):
450456
return _activities_at_offset(q, limit, offset)
451457

452458

459+
def _filter_activitites_by_type(q, activity_type):
460+
'''
461+
Adds a filter to an existing query object to
462+
only show one activity type
463+
'''
464+
users_to_avoid = _activity_stream_get_filtered_users()
465+
if users_to_avoid:
466+
q = q.filter(ckan.model.Activity.activity_type==activity_type)
467+
468+
return q
469+
470+
453471
def _filter_activitites_from_users(q):
454472
'''
455-
Adds a filter to an existing query object ot avoid activities from users
473+
Adds a filter to an existing query object to avoid activities from users
456474
defined in :ref:`ckan.hide_activity_from_users` (defaults to the site user)
457475
'''
458476
users_to_avoid = _activity_stream_get_filtered_users()
Lines changed: 13 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,119 +1,34 @@
11
/* Activity stream
2-
* Handle the loading more of activity items within actiivity streams
2+
* Handle the pagination for activity list
33
*
44
* Options
5-
* - more: are there more items to load
6-
* - context: what's the context for the ajax calls
7-
* - id: what's the id of the context?
8-
* - offset: what's the current offset?
5+
* - page: current page number
96
*/
107
this.ckan.module('activity-stream', function($) {
118
return {
12-
/* options object can be extended using data-module-* attributes */
13-
options : {
14-
more: null,
15-
id: null,
16-
context: null,
17-
offset: null,
18-
loading: false
19-
},
20-
9+
2110
/* Initialises the module setting up elements and event listeners.
2211
*
2312
* Returns nothing.
2413
*/
2514
initialize: function () {
26-
$.proxyAll(this, /_on/);
27-
var options = this.options;
28-
options.more = (options.more == 'True');
29-
this._onBuildLoadMore();
30-
$(window).on('scroll', this._onScrollIntoView);
31-
this._onScrollIntoView();
32-
},
33-
34-
/* Function that tells if el is within the window viewpost
35-
*
36-
* Returns boolean
37-
*/
38-
elementInViewport: function(el) {
39-
var top = el.offsetTop;
40-
var left = el.offsetLeft;
41-
var width = el.offsetWidth;
42-
var height = el.offsetHeight;
43-
while(el.offsetParent) {
44-
el = el.offsetParent;
45-
top += el.offsetTop;
46-
left += el.offsetLeft;
47-
}
48-
return (
49-
top < (window.pageYOffset + window.innerHeight) &&
50-
left < (window.pageXOffset + window.innerWidth) &&
51-
(top + height) > window.pageYOffset &&
52-
(left + width) > window.pageXOffset
15+
$('#activity_types_filter_select').on(
16+
'change',
17+
this._onChangeActivityType
5318
);
5419
},
5520

56-
/* Whenever the window scrolls check if the load more button
57-
* exists, if it's in the view and we're not already loading.
58-
* If all conditions are satisfied... fire a click event on
59-
* the load more button.
60-
*
61-
* Returns nothing
62-
*/
63-
_onScrollIntoView: function() {
64-
var el = $('.load-more a', this.el);
65-
if (el.length == 1) {
66-
var in_viewport = this.elementInViewport(el[0]);
67-
if (in_viewport && !this.options.loading) {
68-
el.trigger('click');
69-
}
70-
}
71-
},
72-
73-
/* If we are able to load more... then attach the ajax request
74-
* to the load more button.
75-
*
76-
* Returns nothing
77-
*/
78-
_onBuildLoadMore: function() {
79-
var options = this.options;
80-
if (options.more) {
81-
$('.load-more', this.el).on('click', 'a', this._onLoadMoreClick);
82-
options.offset = $('.item', this.el).length;
83-
}
84-
},
85-
86-
/* Fires when someone clicks the load more button
87-
* ... and if not loading then make the API call to load
88-
* more activities
21+
22+
/* Filter using the selected
23+
* activity type
8924
*
9025
* Returns nothing
9126
*/
92-
_onLoadMoreClick: function (event) {
93-
event.preventDefault();
94-
var options = this.options;
95-
if (!options.loading) {
96-
options.loading = true;
97-
$('.load-more a', this.el).html(this._('Loading...')).addClass('disabled');
98-
this.sandbox.client.call('GET', options.context+'_activity_list_html', '?id='+options.id+'&offset='+options.offset, this._onActivitiesLoaded);
99-
}
27+
_onChangeActivityType: function (event) {
28+
// event.preventDefault();
29+
url = $("#activity_types_filter_select option:selected" ).data('url');
30+
window.location = url;
10031
},
10132

102-
/* Callback for after the API call
103-
*
104-
* Returns nothing
105-
*/
106-
_onActivitiesLoaded: function(json) {
107-
var options = this.options;
108-
var result = $(json.result);
109-
options.more = ( result.data('module-more') == 'True' );
110-
options.offset += 30;
111-
$('.load-less', result).remove();
112-
$('.load-more', this.el).remove();
113-
$('li', result).appendTo(this.el);
114-
this._onBuildLoadMore();
115-
options.loading = false;
116-
}
117-
11833
};
11934
});

ckan/templates/activity_streams/activity_stream_items.html

Lines changed: 0 additions & 26 deletions
This file was deleted.

ckan/templates/package/activity.html

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,68 @@ <h1 class="hide-heading">
88
{{ _('Activity Stream') }}
99
{% endblock %}
1010
</h1>
11-
{% snippet 'snippets/activity_stream.html', activity_stream=activity_stream, id=id, object_type='package' %}
11+
12+
{# allow extensions to continue using this template without new vars #}
13+
{% if activity_types is defined %}
14+
15+
<div
16+
id="activity_types_filter"
17+
style="margin-bottom: 15px;"
18+
data-module="activity-stream"
19+
>
20+
<label for="activity_types_filter_select">Activity type</label>
21+
<select id="activity_types_filter_select">
22+
<option
23+
{% if not activity_type %}selected{% endif %}
24+
data-url="{{ h.url_for('dataset.activity', id=id, page=0) }}"
25+
>{{ _('All activity types') }}</option>
26+
{% for type_ in activity_types %}
27+
<option
28+
{% if activity_type == type_ %}selected{% endif %}
29+
data-url="{{ h.url_for('dataset.activity', id=id, page=0, activity_type=type_) }}"
30+
>{{ type_.title() }}</option>
31+
{% endfor %}
32+
</select>
33+
34+
</div>
35+
{% endif %}
36+
37+
{% if activity_stream|length > 0 %}
38+
{% snippet 'snippets/activity_stream.html', activity_stream=activity_stream, id=id, object_type='package' %}
39+
{% else %}
40+
<p>
41+
{{ _('No activity found') }}
42+
{% if activity_type %}
43+
{{ _('for this type') }}.
44+
{% endif %}
45+
</p>
46+
{% endif %}
47+
48+
{# allow extensions to continue using this template without new vars #}
49+
{% if page is defined %}
50+
51+
<div id="activity_page_buttons" style="margin-top: 25px;">
52+
53+
{% if page > 0 %}
54+
{% set prev_page = page-1 %}
55+
<a
56+
href="{{ h.url_for('dataset.activity', id=id, page=prev_page) }}"
57+
class="btn btn-default btn-rounded"
58+
>{{ _('Previous page') }}
59+
</a>
60+
61+
{% endif %}
62+
{% if has_more %}
63+
{% set next_page = page + 1 %}
64+
<a
65+
href="{{ h.url_for('dataset.activity', id=id, page=next_page) }}"
66+
class="btn btn-default btn-rounded"
67+
>{{ _('Next page') }}
68+
</a>
69+
70+
{% endif %}
71+
</div>
72+
73+
{% endif %}
74+
1275
{% endblock %}

0 commit comments

Comments
 (0)