Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/okfn/ckan
Browse files Browse the repository at this point in the history
  • Loading branch information
rossjones committed Jun 28, 2012
2 parents 40189ee + 0d234d7 commit 2c2bf54
Show file tree
Hide file tree
Showing 14 changed files with 426 additions and 57 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.txt
Expand Up @@ -6,6 +6,14 @@ v1.8

* requirements have been updated see doc/install-from-source.rst
users will need to do a new pip install (#2592)
* New 'follow' feature. You'll now see a 'Followers' tab on user and dataset
pages, where you can see how many users are following that user or dataset.
If you're logged in, you'll see a 'Follow' button on the pages of datasets
and other users that you can click to follow them. Also when logged in, if
you go to your own user page you'll see a new 'Dashboard' tab where you can
see an activity stream from of all the users and datasets that you're
following. There are also API calls for the follow features, see the Action
API reference documentation.

v1.7.1 2012-06-20
=================
Expand Down
1 change: 1 addition & 0 deletions ckan/config/routing.py
Expand Up @@ -252,6 +252,7 @@ def make_map():
m.connect('/user/edit', action='edit')
# Note: openid users have slashes in their ids, so need the wildcard
# in the route.
m.connect('/user/dashboard', action='dashboard')
m.connect('/user/followers/{id:.*}', action='followers')
m.connect('/user/edit/{id:.*}', action='edit')
m.connect('/user/reset/{id:.*}', action='perform_reset')
Expand Down
1 change: 1 addition & 0 deletions ckan/controllers/api.py
Expand Up @@ -251,6 +251,7 @@ def list(self, ver=None, register=None, subregister=None, id=None):
('dataset', 'activity'): 'package_activity_list',
('group', 'activity'): 'group_activity_list',
('user', 'activity'): 'user_activity_list',
('user', 'dashboard_activity'): 'dashboard_activity_list',
('activity', 'details'): 'activity_detail_list',
}

Expand Down
10 changes: 8 additions & 2 deletions ckan/controllers/user.py
Expand Up @@ -117,8 +117,7 @@ def me(self, locale=None):
h.redirect_to(locale=locale, controller='user',
action='login', id=None)
user_ref = c.userobj.get_reference_preferred_for_uri()
h.redirect_to(locale=locale, controller='user',
action='read', id=user_ref)
h.redirect_to(locale=locale, controller='user', action='dashboard', id=user_ref)

def register(self, data=None, errors=None, error_summary=None):
return self.new(data, errors, error_summary)
Expand Down Expand Up @@ -445,3 +444,10 @@ def followers(self, id=None):
f = get_action('user_follower_list')
c.followers = f(context, {'id': c.user_dict['id']})
return render('user/followers.html')

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)
return render('user/dashboard.html')
17 changes: 16 additions & 1 deletion ckan/lib/helpers.py
Expand Up @@ -506,7 +506,7 @@ def format_icon(_format):

def linked_gravatar(email_hash, size=100, default=None):
return literal(
'<a href="https://gravatar.com/" target="_blank"' +
'<a href="https://gravatar.com/" target="_blank" ' +
'title="%s">' % _('Update your avatar at gravatar.com') +
'%s</a>' % gravatar(email_hash,size,default)
)
Expand Down Expand Up @@ -871,6 +871,20 @@ def follow_count(obj_type, obj_id):
context = {'model' : model, 'session':model.Session, 'user':c.user}
return logic.get_action(action)(context, {'id': obj_id})

def dashboard_activity_stream(user_id):
'''Return the dashboard activity stream of the given user.
:param user_id: the id of the user
:type user_id: string
:returns: an activity stream as an HTML snippet
:rtype: string
'''
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})


# these are the functions that will end up in `h` template helpers
# if config option restrict_template_vars is true
Expand Down Expand Up @@ -927,6 +941,7 @@ def follow_count(obj_type, obj_id):
'unselected_facet_items',
'follow_button',
'follow_count',
'dashboard_activity_stream',
# imported into ckan.lib.helpers
'literal',
'link_to',
Expand Down
137 changes: 136 additions & 1 deletion ckan/logic/action/get.py
Expand Up @@ -1834,6 +1834,7 @@ def user_follower_count(context, data_dict):
:param id: the id or name of the user
:type id: string
:rtype: int
'''
Expand All @@ -1849,6 +1850,7 @@ def dataset_follower_count(context, data_dict):
:param id: the id or name of the dataset
:type id: string
:rtype: int
'''
Expand All @@ -1869,14 +1871,15 @@ def _follower_list(context, data_dict, FollowerClass):
users = [model.User.get(follower.follower_id) for follower in followers]
users = [user for user in users if user is not None]

# Dictize the list of user objects.
# Dictize the list of User objects.
return [model_dictize.user_dictize(user,context) for user in users]

def user_follower_list(context, data_dict):
'''Return the list of users that are following the given user.
:param id: the id or name of the user
:type id: string
:rtype: list of dictionaries
'''
Expand All @@ -1893,6 +1896,7 @@ def dataset_follower_list(context, data_dict):
:param id: the id or name of the dataset
:type id: string
:rtype: list of dictionaries
'''
Expand Down Expand Up @@ -1923,6 +1927,7 @@ def am_following_user(context, data_dict):
:param id: the id or name of the user
:type id: string
:rtype: boolean
'''
Expand All @@ -1940,6 +1945,7 @@ def am_following_dataset(context, data_dict):
:param id: the id or name of the dataset
:type id: string
:rtype: boolean
'''
Expand All @@ -1951,3 +1957,132 @@ def am_following_dataset(context, data_dict):

return _am_following(context, data_dict,
context['model'].UserFollowingDataset)

def user_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
'''
schema = context.get('schema') or (
ckan.logic.schema.default_follow_user_schema())
data_dict, errors = _validate(data_dict, schema, context)
if errors:
raise ValidationError(errors, ckan.logic.action.error_summary(errors))
return ckan.model.UserFollowingUser.followee_count(data_dict['id'])

def dataset_followee_count(context, data_dict):
'''Return the number of datasets that are followed by the given user.
:param id: the id of the user
:type id: string
:rtype: int
'''
schema = context.get('schema') or (
ckan.logic.schema.default_follow_user_schema())
data_dict, errors = _validate(data_dict, schema, context)
if errors:
raise ValidationError(errors, ckan.logic.action.error_summary(errors))
return ckan.model.UserFollowingDataset.followee_count(data_dict['id'])

def user_followee_list(context, data_dict):
'''Return the list of users that are followed by the given user.
:param id: the id of the user
:type id: string
:rtype: list of dictionaries
'''
schema = context.get('schema') or (
ckan.logic.schema.default_follow_user_schema())
data_dict, errors = _validate(data_dict, schema, context)
if errors:
raise ValidationError(errors, ckan.logic.action.error_summary(errors))

# Get the list of Follower objects.
model = context['model']
user_id = data_dict.get('id')
followees = model.UserFollowingUser.followee_list(user_id)

# Convert the list of Follower objects to a list of User objects.
users = [model.User.get(followee.object_id) for followee in followees]
users = [user for user in users if user is not None]

# Dictize the list of User objects.
return [model_dictize.user_dictize(user, context) for user in users]

def dataset_followee_list(context, data_dict):
'''Return the list of datasets that are followed by the given user.
:param id: the id or name of the user
:type id: string
:rtype: list of dictionaries
'''
schema = context.get('schema') or (
ckan.logic.schema.default_follow_user_schema())
data_dict, errors = _validate(data_dict, schema, context)
if errors:
raise ValidationError(errors, ckan.logic.action.error_summary(errors))

# Get the list of Follower objects.
model = context['model']
user_id = data_dict.get('id')
followees = model.UserFollowingDataset.followee_list(user_id)

# Convert the list of Follower objects to a list of Package objects.
datasets = [model.Package.get(followee.object_id) for followee in followees]
datasets = [dataset for dataset in datasets if dataset is not None]

# Dictize the list of Package objects.
return [model_dictize.package_dictize(dataset, context) for dataset in datasets]

def dashboard_activity_list(context, data_dict):
'''Return the dashboard activity stream of the given user.
:param id: the id or name of the user
:type id: string
:rtype: list of dictionaries
'''
model = context['model']
user_id = _get_or_bust(data_dict, 'id')

activity_query = model.Session.query(model.Activity)
user_query = activity_query;
user_followees_query = activity_query.join(model.UserFollowingUser, model.UserFollowingUser.object_id == model.Activity.user_id)
dataset_followees_query = activity_query.join(model.UserFollowingDataset, model.UserFollowingDataset.object_id == model.Activity.object_id)

user_query = user_query.filter(model.Activity.user_id==user_id)
user_followees_query = user_followees_query.filter(model.UserFollowingUser.follower_id==user_id)
dataset_followees_query = dataset_followees_query.filter(model.UserFollowingDataset.follower_id==user_id)

query = user_query.union(user_followees_query).union(dataset_followees_query)
query = query.order_by(_desc(model.Activity.timestamp))
query = query.limit(15)
activity_objects = query.all()

return model_dictize.activity_list_dictize(activity_objects, context)

def dashboard_activity_list_html(context, data_dict):
'''Return the dashboard activity stream of the given user as HTML.
The activity stream is rendered as a snippet of HTML meant to be included
in an HTML page, i.e. it doesn't have any HTML header or footer.
:param id: The id or name of the user.
:type id: string
:rtype: string
'''
activity_stream = dashboard_activity_list(context, data_dict)
return _activity_list_to_html(context, activity_stream)
74 changes: 52 additions & 22 deletions ckan/model/follower.py
Expand Up @@ -27,24 +27,39 @@ def get(self, follower_id, object_id):
return query.first()

@classmethod
def follower_count(cls, object_id):
'''Return the number of users following a user.'''
def is_following(cls, follower_id, object_id):
'''Return True if follower_id is currently following object_id, False
otherwise.
'''
return UserFollowingUser.get(follower_id, object_id) is not None


@classmethod
def followee_count(cls, follower_id):
'''Return the number of users followed by a user.'''
return meta.Session.query(UserFollowingUser).filter(
UserFollowingUser.object_id == object_id).count()
UserFollowingUser.follower_id == follower_id).count()

@classmethod
def follower_list(cls, object_id):
'''Return a list of all of the followers of a user.'''
def followee_list(cls, follower_id):
'''Return a list of users followed by a user.'''
return meta.Session.query(UserFollowingUser).filter(
UserFollowingUser.object_id == object_id).all()
UserFollowingUser.follower_id == follower_id).all()


@classmethod
def is_following(cls, follower_id, object_id):
'''Return True if follower_id is currently following object_id, False
otherwise.
def follower_count(cls, user_id):
'''Return the number of followers of a user.'''
return meta.Session.query(UserFollowingUser).filter(
UserFollowingUser.object_id == user_id).count()

@classmethod
def follower_list(cls, user_id):
'''Return a list of followers of a user.'''
return meta.Session.query(UserFollowingUser).filter(
UserFollowingUser.object_id == user_id).all()

'''
return UserFollowingUser.get(follower_id, object_id) is not None

user_following_user_table = sqlalchemy.Table('user_following_user',
meta.metadata,
Expand Down Expand Up @@ -85,24 +100,39 @@ def get(self, follower_id, object_id):
return query.first()

@classmethod
def follower_count(cls, object_id):
'''Return the number of users following a dataset.'''
def is_following(cls, follower_id, object_id):
'''Return True if follower_id is currently following object_id, False
otherwise.
'''
return UserFollowingDataset.get(follower_id, object_id) is not None


@classmethod
def followee_count(cls, follower_id):
'''Return the number of datasets followed by a user.'''
return meta.Session.query(UserFollowingDataset).filter(
UserFollowingDataset.object_id == object_id).count()
UserFollowingDataset.follower_id == follower_id).count()

@classmethod
def follower_list(cls, object_id):
'''Return a list of all of the followers of a dataset.'''
def followee_list(cls, follower_id):
'''Return a list of datasets followed by a user.'''
return meta.Session.query(UserFollowingDataset).filter(
UserFollowingDataset.object_id == object_id).all()
UserFollowingDataset.follower_id == follower_id).all()


@classmethod
def is_following(cls, follower_id, object_id):
'''Return True if follower_id is currently following object_id, False
otherwise.
def follower_count(cls, dataset_id):
'''Return the number of followers of a dataset.'''
return meta.Session.query(UserFollowingDataset).filter(
UserFollowingDataset.object_id == dataset_id).count()

@classmethod
def follower_list(cls, dataset_id):
'''Return a list of followers of a dataset.'''
return meta.Session.query(UserFollowingDataset).filter(
UserFollowingDataset.object_id == dataset_id).all()

'''
return UserFollowingDataset.get(follower_id, object_id) is not None

user_following_dataset_table = sqlalchemy.Table('user_following_dataset',
meta.metadata,
Expand Down

0 comments on commit 2c2bf54

Please sign in to comment.