From d6f3492975c6822752d87a0b9e9d497f8a57ef2a Mon Sep 17 00:00:00 2001 From: Rocky Duan Date: Sun, 12 Aug 2012 22:55:55 -0700 Subject: [PATCH] replaced comment client sym link --- .gitignore | 1 + lms/lib/comment_client | 1 - lms/lib/comment_client/__init__.py | 2 + lms/lib/comment_client/comment_client.py | 153 +++++++++++++++++++ lms/lib/comment_client/legacy.py | 180 +++++++++++++++++++++++ lms/lib/comment_client/models.py | 106 +++++++++++++ lms/lib/comment_client/requirements.txt | 1 + lms/lib/comment_client/utils.py | 38 +++++ 8 files changed, 481 insertions(+), 1 deletion(-) delete mode 120000 lms/lib/comment_client create mode 100644 lms/lib/comment_client/__init__.py create mode 100644 lms/lib/comment_client/comment_client.py create mode 100644 lms/lib/comment_client/legacy.py create mode 100644 lms/lib/comment_client/models.py create mode 100644 lms/lib/comment_client/requirements.txt create mode 100644 lms/lib/comment_client/utils.py diff --git a/.gitignore b/.gitignore index 28b78aedbc..3653c832fa 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ Gemfile.lock .env/ lms/static/sass/*.css cms/static/sass/*.css +lms/lib/comment_client/python diff --git a/lms/lib/comment_client b/lms/lib/comment_client deleted file mode 120000 index 326be91892..0000000000 --- a/lms/lib/comment_client +++ /dev/null @@ -1 +0,0 @@ -/Users/dementrock/coding/cs_comments_client_python/comment_client \ No newline at end of file diff --git a/lms/lib/comment_client/__init__.py b/lms/lib/comment_client/__init__.py new file mode 100644 index 0000000000..ecade68b50 --- /dev/null +++ b/lms/lib/comment_client/__init__.py @@ -0,0 +1,2 @@ +from comment_client import * +from utils import CommentClientError, CommentClientUnknownError diff --git a/lms/lib/comment_client/comment_client.py b/lms/lib/comment_client/comment_client.py new file mode 100644 index 0000000000..d8d4f68f3b --- /dev/null +++ b/lms/lib/comment_client/comment_client.py @@ -0,0 +1,153 @@ +import requests +import json +import models + +from utils import * + +SERVICE_HOST = 'http://localhost:4567' + +PREFIX = SERVICE_HOST + '/api/v1' + +class Comment(models.Model): + accessible_fields = [ + 'id', 'body', 'anonymous', 'course_id', + 'endorsed', 'parent_id', 'thread_id', + 'username', 'votes', 'user_id', 'closed', + 'created_at', 'updated_at', 'depth', + 'at_position_list', + ] + base_url = "{prefix}/comments".format(prefix=PREFIX) + type = 'comment' + + +class Thread(models.Model): + accessible_fields = [ + 'id', 'title', 'body', 'anonymous', + 'course_id', 'closed', 'tags', 'votes', + 'commentable_id', 'username', 'user_id', + 'created_at', 'updated_at', 'comments_count', + 'at_position_list', 'children', + ] + base_url = "{prefix}/threads".format(prefix=PREFIX) + default_retrieve_params = {'recursive': False} + type = 'thread' + + @classmethod + def search(cls, query_params, *args, **kwargs): + default_params = {'page': 1, + 'per_page': 20, + 'course_id': query_params['course_id'], + 'recursive': False} + params = merge_dict(default_params, strip_none(query_params)) + if query_params['text'] or query_params['tags']: + url = cls.url(action='search') + else: + url = cls.url(action='get_all', commentable_id=params['commentable_id']) + del params['commentable_id'] + response = perform_request('get', url, params, *args, **kwargs) + return response.get('collection', []), response.get('page', 1), response.get('num_pages', 1) + + @classmethod + def url_for_threads(cls, *args, **kwargs): + return "{prefix}/{commentable_id}/threads".format(prefix=PREFIX, commentable_id=kwargs.get('commentable_id')) + + @classmethod + def url_for_search_threads(cls, *args, **kwargs): + return "{prefix}/search/threads".format(prefix=PREFIX) + + @classmethod + def url(cls, *args, **kwargs): + action = kwargs.get('action') + if action in ['get_all', 'post']: + return cls.url_for_threads(commentable_id=kwargs.get('commentable_id')) + elif action == 'search': + return cls.url_for_search_threads() + else: + return super(Thread, cls).url(*args, **kwargs) + + def _retrieve(self, *args, **kwargs): + url = self.url(action='get', id=self.id) + response = perform_request('get', url, {'recursive': kwargs.get('recursive')}) + self.update_attributes(**response) + +class Commentable(models.Model): + + base_url = "{prefix}/commentables".format(prefix=PREFIX) + type = 'commentable' + +class User(models.Model): + + accessible_fields = ['username', 'follower_ids', 'upvoted_ids', 'downvoted_ids', + 'id', 'external_id', 'subscribed_user_ids', 'children', + 'subscribed_thread_ids', 'subscribed_commentable_ids', + ] + base_url = "{prefix}/users".format(prefix=PREFIX) + default_retrieve_params = {'complete': True} + type = 'user' + + @classmethod + def from_django_user(cls, user): + return cls(id=str(user.id)) + + def follow(self, source): + params = {'source_type': source.type, 'source_id': source.id} + response = perform_request('post', _url_for_subscription(self.id), params) + + def unfollow(self, source): + params = {'source_type': source.type, 'source_id': source.id} + response = perform_request('delete', _url_for_subscription(self.id), params) + + def vote(self, voteable, value): + if voteable.type == 'thread': + url = _url_for_vote_thread(voteable.id) + elif voteable.type == 'comment': + url = _url_for_vote_comment(voteable.id) + else: + raise CommentClientError("Can only vote / unvote for threads or comments") + params = {'user_id': self.id, 'value': value} + request = perform_request('put', url, params) + voteable.update_attributes(request) + + def unvote(self, voteable): + if voteable.type == 'thread': + url = _url_for_vote_thread(voteable.id) + elif voteable.type == 'comment': + url = _url_for_vote_comment(voteable.id) + else: + raise CommentClientError("Can only vote / unvote for threads or comments") + params = {'user_id': self.id} + request = perform_request('delete', url, params) + voteable.update_attributes(request) + +def search_similar_threads(course_id, recursive=False, query_params={}, *args, **kwargs): + default_params = {'course_id': course_id, 'recursive': recursive} + attributes = dict(default_params.items() + query_params.items()) + return perform_request('get', _url_for_search_similar_threads(), attributes, *args, **kwargs) + +def search_recent_active_threads(course_id, recursive=False, query_params={}, *args, **kwargs): + default_params = {'course_id': course_id, 'recursive': recursive} + attributes = dict(default_params.items() + query_params.items()) + return perform_request('get', _url_for_search_recent_active_threads(), attributes, *args, **kwargs) + +def search_trending_tags(course_id, query_params={}, *args, **kwargs): + default_params = {'course_id': course_id} + attributes = dict(default_params.items() + query_params.items()) + return perform_request('get', _url_for_search_trending_tags(), attributes, *args, **kwargs) + +def _url_for_search_similar_threads(): + return "{prefix}/search/threads/more_like_this".format(prefix=PREFIX) + +def _url_for_search_recent_active_threads(): + return "{prefix}/search/threads/recent_active".format(prefix=PREFIX) + +def _url_for_search_trending_tags(): + return "{prefix}/search/tags/trending".format(prefix=PREFIX) + +def _url_for_subscription(user_id): + return "{prefix}/users/{user_id}/subscriptions".format(prefix=PREFIX, user_id=user_id) + +def _url_for_vote_comment(comment_id): + return "{prefix}/comments/{comment_id}/votes".format(prefix=PREFIX, comment_id=comment_id) + +def _url_for_vote_thread(thread_id): + return "{prefix}/threads/{thread_id}/votes".format(prefix=PREFIX, thread_id=thread_id) diff --git a/lms/lib/comment_client/legacy.py b/lms/lib/comment_client/legacy.py new file mode 100644 index 0000000000..fc87bcaf4f --- /dev/null +++ b/lms/lib/comment_client/legacy.py @@ -0,0 +1,180 @@ +def delete_threads(commentable_id, *args, **kwargs): + return _perform_request('delete', _url_for_commentable_threads(commentable_id), *args, **kwargs) + +def get_threads(commentable_id, recursive=False, query_params={}, *args, **kwargs): + default_params = {'page': 1, 'per_page': 20, 'recursive': recursive} + attributes = dict(default_params.items() + query_params.items()) + response = _perform_request('get', _url_for_threads(commentable_id), \ + attributes, *args, **kwargs) + return response.get('collection', []), response.get('page', 1), response.get('num_pages', 1) + +def search_threads(course_id, recursive=False, query_params={}, *args, **kwargs): + default_params = {'page': 1, 'per_page': 20, 'course_id': course_id, 'recursive': recursive} + attributes = dict(default_params.items() + query_params.items()) + response = _perform_request('get', _url_for_search_threads(), \ + attributes, *args, **kwargs) + return response.get('collection', []), response.get('page', 1), response.get('num_pages', 1) + +def search_similar_threads(course_id, recursive=False, query_params={}, *args, **kwargs): + default_params = {'course_id': course_id, 'recursive': recursive} + attributes = dict(default_params.items() + query_params.items()) + return _perform_request('get', _url_for_search_similar_threads(), attributes, *args, **kwargs) + +def search_recent_active_threads(course_id, recursive=False, query_params={}, *args, **kwargs): + default_params = {'course_id': course_id, 'recursive': recursive} + attributes = dict(default_params.items() + query_params.items()) + return _perform_request('get', _url_for_search_recent_active_threads(), attributes, *args, **kwargs) + +def search_trending_tags(course_id, query_params={}, *args, **kwargs): + default_params = {'course_id': course_id} + attributes = dict(default_params.items() + query_params.items()) + return _perform_request('get', _url_for_search_trending_tags(), attributes, *args, **kwargs) + +def create_user(attributes, *args, **kwargs): + return _perform_request('post', _url_for_users(), attributes, *args, **kwargs) + +def update_user(user_id, attributes, *args, **kwargs): + return _perform_request('put', _url_for_user(user_id), attributes, *args, **kwargs) + +def get_threads_tags(*args, **kwargs): + return _perform_request('get', _url_for_threads_tags(), {}, *args, **kwargs) + +def tags_autocomplete(value, *args, **kwargs): + return _perform_request('get', _url_for_threads_tags_autocomplete(), {'value': value}, *args, **kwargs) + +def create_thread(commentable_id, attributes, *args, **kwargs): + return _perform_request('post', _url_for_threads(commentable_id), attributes, *args, **kwargs) + +def get_thread(thread_id, recursive=False, *args, **kwargs): + return _perform_request('get', _url_for_thread(thread_id), {'recursive': recursive}, *args, **kwargs) + +def update_thread(thread_id, attributes, *args, **kwargs): + return _perform_request('put', _url_for_thread(thread_id), attributes, *args, **kwargs) + +def create_comment(thread_id, attributes, *args, **kwargs): + return _perform_request('post', _url_for_thread_comments(thread_id), attributes, *args, **kwargs) + +def delete_thread(thread_id, *args, **kwargs): + return _perform_request('delete', _url_for_thread(thread_id), *args, **kwargs) + +def get_comment(comment_id, recursive=False, *args, **kwargs): + return _perform_request('get', _url_for_comment(comment_id), {'recursive': recursive}, *args, **kwargs) + +def update_comment(comment_id, attributes, *args, **kwargs): + return _perform_request('put', _url_for_comment(comment_id), attributes, *args, **kwargs) + +def create_sub_comment(comment_id, attributes, *args, **kwargs): + return _perform_request('post', _url_for_comment(comment_id), attributes, *args, **kwargs) + +def delete_comment(comment_id, *args, **kwargs): + return _perform_request('delete', _url_for_comment(comment_id), *args, **kwargs) + +def vote_for_comment(comment_id, user_id, value, *args, **kwargs): + return _perform_request('put', _url_for_vote_comment(comment_id), {'user_id': user_id, 'value': value}, *args, **kwargs) + +def undo_vote_for_comment(comment_id, user_id, *args, **kwargs): + return _perform_request('delete', _url_for_vote_comment(comment_id), {'user_id': user_id}, *args, **kwargs) + +def vote_for_thread(thread_id, user_id, value, *args, **kwargs): + return _perform_request('put', _url_for_vote_thread(thread_id), {'user_id': user_id, 'value': value}, *args, **kwargs) + +def undo_vote_for_thread(thread_id, user_id, *args, **kwargs): + return _perform_request('delete', _url_for_vote_thread(thread_id), {'user_id': user_id}, *args, **kwargs) + +def get_notifications(user_id, *args, **kwargs): + return _perform_request('get', _url_for_notifications(user_id), *args, **kwargs) + +def get_user_info(user_id, complete=True, *args, **kwargs): + return _perform_request('get', _url_for_user(user_id), {'complete': complete}, *args, **kwargs) + +def subscribe(user_id, subscription_detail, *args, **kwargs): + return _perform_request('post', _url_for_subscription(user_id), subscription_detail, *args, **kwargs) + +def subscribe_user(user_id, followed_user_id, *args, **kwargs): + return subscribe(user_id, {'source_type': 'user', 'source_id': followed_user_id}) + +follow = subscribe_user + +def subscribe_thread(user_id, thread_id, *args, **kwargs): + return subscribe(user_id, {'source_type': 'thread', 'source_id': thread_id}) + +def subscribe_commentable(user_id, commentable_id, *args, **kwargs): + return subscribe(user_id, {'source_type': 'other', 'source_id': commentable_id}) + +def unsubscribe(user_id, subscription_detail, *args, **kwargs): + return _perform_request('delete', _url_for_subscription(user_id), subscription_detail, *args, **kwargs) + +def unsubscribe_user(user_id, followed_user_id, *args, **kwargs): + return unsubscribe(user_id, {'source_type': 'user', 'source_id': followed_user_id}) + +unfollow = unsubscribe_user + +def unsubscribe_thread(user_id, thread_id, *args, **kwargs): + return unsubscribe(user_id, {'source_type': 'thread', 'source_id': thread_id}) + +def unsubscribe_commentable(user_id, commentable_id, *args, **kwargs): + return unsubscribe(user_id, {'source_type': 'other', 'source_id': commentable_id}) + +def _perform_request(method, url, data_or_params=None, *args, **kwargs): + if method in ['post', 'put', 'patch']: + response = requests.request(method, url, data=data_or_params) + else: + response = requests.request(method, url, params=data_or_params) + if 200 < response.status_code < 500: + raise CommentClientError(response.text) + elif response.status_code == 500: + raise CommentClientUnknownError(response.text) + else: + if kwargs.get("raw", False): + return response.text + else: + return json.loads(response.text) + +def _url_for_threads(commentable_id): + return "{prefix}/{commentable_id}/threads".format(prefix=PREFIX, commentable_id=commentable_id) + +def _url_for_thread(thread_id): + return "{prefix}/threads/{thread_id}".format(prefix=PREFIX, thread_id=thread_id) + +def _url_for_thread_comments(thread_id): + return "{prefix}/threads/{thread_id}/comments".format(prefix=PREFIX, thread_id=thread_id) + +def _url_for_comment(comment_id): + return "{prefix}/comments/{comment_id}".format(prefix=PREFIX, comment_id=comment_id) + +def _url_for_vote_comment(comment_id): + return "{prefix}/comments/{comment_id}/votes".format(prefix=PREFIX, comment_id=comment_id) + +def _url_for_vote_thread(thread_id): + return "{prefix}/threads/{thread_id}/votes".format(prefix=PREFIX, thread_id=thread_id) + +def _url_for_notifications(user_id): + return "{prefix}/users/{user_id}/notifications".format(prefix=PREFIX, user_id=user_id) + +def _url_for_subscription(user_id): + return "{prefix}/users/{user_id}/subscriptions".format(prefix=PREFIX, user_id=user_id) + +def _url_for_user(user_id): + return "{prefix}/users/{user_id}".format(prefix=PREFIX, user_id=user_id) + +def _url_for_search_threads(): + return "{prefix}/search/threads".format(prefix=PREFIX) + +def _url_for_search_similar_threads(): + return "{prefix}/search/threads/more_like_this".format(prefix=PREFIX) + +def _url_for_search_recent_active_threads(): + return "{prefix}/search/threads/recent_active".format(prefix=PREFIX) + +def _url_for_search_trending_tags(): + return "{prefix}/search/tags/trending".format(prefix=PREFIX) + +def _url_for_threads_tags(): + return "{prefix}/threads/tags".format(prefix=PREFIX) + +def _url_for_threads_tags_autocomplete(): + return "{prefix}/threads/tags/autocomplete".format(prefix=PREFIX) + +def _url_for_users(): + return "{prefix}/users".format(prefix=PREFIX) + diff --git a/lms/lib/comment_client/models.py b/lms/lib/comment_client/models.py new file mode 100644 index 0000000000..8d1349040f --- /dev/null +++ b/lms/lib/comment_client/models.py @@ -0,0 +1,106 @@ +from utils import * + +class Model(object): + + accessible_fields = ['id'] + base_url = None + default_retrieve_params = {} + + DEFAULT_ACTIONS_WITH_ID = ['get', 'put', 'delete'] + DEFAULT_ACTIONS_WITHOUT_ID = ['get_all', 'post'] + DEFAULT_ACTIONS = DEFAULT_ACTIONS_WITH_ID + DEFAULT_ACTIONS_WITHOUT_ID + + def __init__(self, *args, **kwargs): + self.attributes = extract(kwargs, self.accessible_fields) + self.retrieved = False + + def __getattr__(self, name): + if name == 'id': + return self.attributes.get('id', None) + try: + return self.attributes[name] + except KeyError: + if self.retrieved or self.id == None: + raise AttributeError("Field {0} does not exist".format(name)) + self.retrieve() + return self.__getattr__(name) + + def __setattr__(self, name, value): + if name == 'attributes' or name not in self.accessible_fields: + super(Model, self).__setattr__(name, value) + else: + self.attributes[name] = value + + def __getitem__(self, key): + if key not in self.accessible_fields: + raise KeyError("Field {0} does not exist".format(key)) + return self.attributes.get(key) + + def __setitem__(self, key, value): + if key not in self.accessible_fields: + raise KeyError("Field {0} does not exist".format(key)) + self.attributes.__setitem__(key, value) + + def get(self, *args, **kwargs): + return self.attributes.get(*args, **kwargs) + + def to_dict(self): + self.retrieve() + return self.attributes + + def retrieve(self, *args, **kwargs): + if not self.retrieved: + self._retrieve(*args, **kwargs) + self.retrieved = True + return self + + def _retrieve(self, *args, **kwargs): + url = self.url(action='get', id=self.id) + response = perform_request('get', url, self.default_retrieve_params) + self.update_attributes(**response) + + @classmethod + def find(cls, id): + return cls(id=id) + + def update_attributes(self, *args, **kwargs): + for k, v in kwargs.items(): + if k in self.accessible_fields: + self.__setattr__(k, v) + else: + raise AttributeError("Field {0} does not exist".format(k)) + + def save(self): + if self.id: # if we have id already, treat this as an update + url = self.url(action='put', id=self.id) + response = perform_request('put', url, self.attributes) + else: # otherwise, treat this as an insert + url = self.url(action='post', id=self.id) + response = perform_request('post', url, self.attributes) + self.retrieved = True + self.update_attributes(**response) + + @classmethod + def url_with_id(cls, *args, **kwargs): + return cls.base_url + '/' + str(kwargs.get('id')) + + @classmethod + def url_without_id(cls, *args, **kwargs): + return cls.base_url + + @classmethod + def url(cls, *args, **kwargs): + if cls.base_url is None: + raise CommentClientError("Must provide base_url when using default url function") + id = kwargs.get('id') + action = kwargs.get('action') + if not action: + raise CommentClientError("Must provide action") + elif action not in cls.DEFAULT_ACTIONS: + raise ValueError("Invalid action {0}. The supported action must be in {1}".format(action, str(cls.DEFAULT_ACTIONS))) + elif action in cls.DEFAULT_ACTIONS_WITH_ID: + if not id: + raise CommentClientError("Cannot perform action {0} without id".format(action)) + return cls.url_with_id(id=id) + else: # action must be in DEFAULT_ACTIONS_WITHOUT_ID now + return cls.url_without_id() diff --git a/lms/lib/comment_client/requirements.txt b/lms/lib/comment_client/requirements.txt new file mode 100644 index 0000000000..f2293605cf --- /dev/null +++ b/lms/lib/comment_client/requirements.txt @@ -0,0 +1 @@ +requests diff --git a/lms/lib/comment_client/utils.py b/lms/lib/comment_client/utils.py new file mode 100644 index 0000000000..c3ad4b612e --- /dev/null +++ b/lms/lib/comment_client/utils.py @@ -0,0 +1,38 @@ +import requests +import json + +def strip_none(dic): + def _is_none(v): + return v is None or (isinstance(v, str) and len(v.strip()) == 0) + return dict([(k, v) for k, v in dic.iteritems() if not _is_none(v)]) + +def extract(dic, keys): + return strip_none({k: dic.get(k) for k in keys}) + +def merge_dict(dic1, dic2): + return dict(dic1.items() + dic2.items()) + +def perform_request(method, url, data_or_params=None, *args, **kwargs): + if method in ['post', 'put', 'patch']: + response = requests.request(method, url, data=data_or_params) + else: + response = requests.request(method, url, params=data_or_params) + if 200 < response.status_code < 500: + raise CommentClientError(response.text) + elif response.status_code == 500: + raise CommentClientUnknownError(response.text) + else: + if kwargs.get("raw", False): + return response.text + else: + return json.loads(response.text) + +class CommentClientError(Exception): + def __init__(self, msg): + self.message = msg + + def __str__(self): + return repr(self.message) + +class CommentClientUnknownError(CommentClientError): + pass