diff options
author | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2013-07-21 00:09:14 -0400 |
---|---|---|
committer | Evgeny Fadeev <evgeny.fadeev@gmail.com> | 2013-07-21 00:09:14 -0400 |
commit | 7ab4d396ec035d5325fc6bfd3bc9edb896ab9908 (patch) | |
tree | 4743ad021ccca2a5e5fce0d981d269f786d93e8e | |
parent | 8bc6e3521e6469b174c35827ad241d5b191de3cd (diff) | |
download | askbot-7ab4d396ec035d5325fc6bfd3bc9edb896ab9908.tar.gz askbot-7ab4d396ec035d5325fc6bfd3bc9edb896ab9908.tar.bz2 askbot-7ab4d396ec035d5325fc6bfd3bc9edb896ab9908.zip |
made api more uniform
-rw-r--r-- | askbot/doc/source/api.rst | 31 | ||||
-rw-r--r-- | askbot/models/__init__.py | 2 | ||||
-rw-r--r-- | askbot/tests/__init__.py | 1 | ||||
-rw-r--r-- | askbot/tests/api_v1_tests.py | 52 | ||||
-rw-r--r-- | askbot/tests/page_load_tests.py | 45 | ||||
-rw-r--r-- | askbot/urls.py | 10 | ||||
-rw-r--r-- | askbot/views/__init__.py | 2 | ||||
-rw-r--r-- | askbot/views/api.py | 202 | ||||
-rw-r--r-- | askbot/views/api_v1.py | 199 |
9 files changed, 284 insertions, 260 deletions
diff --git a/askbot/doc/source/api.rst b/askbot/doc/source/api.rst index c3a79a4d..dee2a99b 100644 --- a/askbot/doc/source/api.rst +++ b/askbot/doc/source/api.rst @@ -5,9 +5,28 @@ Askbot API Askbot has API to access forum data in read-only mode. Current version of API is 1. -All urls start with `/api/v1` and the following endpoints are available:: -*`/api/v1/info/forum/` -*`/api/v1/info/users/` -*`/api/v1/info/users/<user_id>/` -*`/api/v1/info/questions/` -*`/api/v1/info/questions/<question_id>/` +All data is returned in json format. + +All urls start with `/api/v1/` and the following endpoints are available: + +`/api/v1/info/` +--------------- + +Returns basic parameters of the site. + +`/api/v1/users/` +---------------- + +Returns, count, number of pages and basic data for each user. +Optional parameters: page (<int>), sort (reputation|oldest|recent|username) + +`/api/v1/users/<user_id>/` +-------------------------- + +Returns basic information about a given user. + +`/api/v1/questions/` +-------------------- + +*`/api/v1/questions/` +*`/api/v1/questions/<question_id>/` diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index 644af722..195aa1ec 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -277,7 +277,7 @@ def user_get_default_avatar_url(self, size): """ return skin_utils.get_media_url(askbot_settings.DEFAULT_AVATAR_URL) -def user_get_avatar_url(self, size): +def user_get_avatar_url(self, size=48): """returns avatar url - by default - gravatar, but if application django-avatar is installed it will use avatar provided through that app diff --git a/askbot/tests/__init__.py b/askbot/tests/__init__.py index 7e7e3c48..87784266 100644 --- a/askbot/tests/__init__.py +++ b/askbot/tests/__init__.py @@ -23,3 +23,4 @@ from askbot.tests.user_model_tests import UserModelTests from askbot.tests.user_views_tests import * from askbot.tests.utils_tests import * from askbot.tests.view_context_tests import * +from askbot.tests.api_v1_tests import * diff --git a/askbot/tests/api_v1_tests.py b/askbot/tests/api_v1_tests.py new file mode 100644 index 00000000..1b1417f4 --- /dev/null +++ b/askbot/tests/api_v1_tests.py @@ -0,0 +1,52 @@ +from askbot.tests.utils import AskbotTestCase +from django.core.urlresolvers import reverse +from django.utils import simplejson + +class ApiV1Tests(AskbotTestCase): + def test_api_v1_user(self): + user = self.create_user('apiuser') + response = self.client.get(reverse('api_v1_user', args=(user.id,))) + response_data = simplejson.loads(response.content) + expected_keys = set(['id', 'name', 'reputation', 'questions', 'comments', + 'avatar', 'joined_at', 'last_seen_at', 'answers']) + self.assertEqual(expected_keys, set(response_data.keys())) + + def test_api_v1_info(self): + response = self.client.get(reverse('api_v1_info')) + response_data = simplejson.loads(response.content) + expected_keys = set(['answers', 'comments', 'users', 'groups', 'questions']) + self.assertEqual(expected_keys, set(response_data.keys())) + + def test_api_v1_users(self): + self.create_user('somebody') + response = self.client.get(reverse('api_v1_users')) + response_data = simplejson.loads(response.content) + expected_keys = set(['pages', 'count', 'users']) + self.assertEqual(expected_keys, set(response_data.keys())) + + expected_keys = set(['id', 'avatar', 'name', + 'joined_at', 'last_seen_at', 'reputation']) + self.assertEqual(expected_keys, set(response_data['users'][0].keys())) + + def test_api_v1_questions(self): + user = self.create_user('user') + self.post_question(user=user) + response = self.client.get(reverse('api_v1_questions')) + response_data = simplejson.loads(response.content) + expected_keys = set(['count', 'pages', 'questions']) + self.assertEqual(expected_keys, set(response_data.keys())) + + expected_keys = set([ + 'id', 'view_count', 'title', 'answer_count', + 'last_activity_by', 'last_activity_at', 'author', + 'url', 'tags', 'added_at', 'score' + ]) + self.assertEqual(expected_keys, set(response_data['questions'][0].keys())) + + author_info = response_data['questions'][0]['author'] + self.assertEqual(set(author_info.keys()), set(['id', 'name'])) + self.assertEqual(set(author_info.values()), set([user.id, user.username])) + + last_act_info = response_data['questions'][0]['last_activity_by'] + self.assertEqual(set(last_act_info.keys()), set(['id', 'name'])) + self.assertEqual(set(last_act_info.values()), set([user.id, user.username])) diff --git a/askbot/tests/page_load_tests.py b/askbot/tests/page_load_tests.py index 86588441..be54a1dc 100644 --- a/askbot/tests/page_load_tests.py +++ b/askbot/tests/page_load_tests.py @@ -189,51 +189,6 @@ class PageLoadTestCase(AskbotTestCase): response_data = simplejson.loads(response.content) self.assertEqual(len(response_data), 1) - def test_api_user_info(self): - user = self.create_user('apiuser') - response = self.client.get(reverse('api_user_info', args=(user.id,))) - response_data = simplejson.loads(response.content) - expected_keys = ('username', 'reputation', 'questions', 'answers') - for key in expected_keys: - self.assertTrue(key in response_data.keys()) - - def test_api_forum_info(self): - response = self.client.get(reverse('api_forum_info')) - response_data = simplejson.loads(response.content) - expected_keys = ('answers', 'comments', 'users', 'questions') - for key in expected_keys: - self.assertTrue(key in response_data.keys()) - - def test_api_users_info(self): - response = self.client.get(reverse('api_users_info')) - response_data = simplejson.loads(response.content) - expected_keys = ('total_pages', 'count', 'user_list') - for key in expected_keys: - self.assertTrue(key in response_data.keys()) - - expected_keys = ('id', 'username', 'date_joined', 'reputation') - for key in expected_keys: - self.assertTrue(key in response_data['user_list'][0].keys()) - - def test_api_latest_questions(self): - response = self.client.get(reverse('api_latest_questions')) - response_data = simplejson.loads(response.content) - expected_keys = ('query_data', 'question_count', 'query_string', - 'page_size', 'non_existing_tags', 'question_list') - - for key in expected_keys: - self.assertTrue(key in response_data.keys()) - - expected_keys = ('tags', 'sort_order', 'ask_query_string') - for key in expected_keys: - self.assertTrue(key in response_data['query_data'].keys()) - - expected_keys = ('id', 'view_count', 'tagnames', - 'title', 'answer_count', - 'last_activity_by__username') - for key in expected_keys: - self.assertTrue(key in response_data['question_list'][0].keys()) - def test_ask_page_disallowed_anonymous(self): self.proto_test_ask_page(False, 302) diff --git a/askbot/urls.py b/askbot/urls.py index 07b701e0..8759754c 100644 --- a/askbot/urls.py +++ b/askbot/urls.py @@ -573,11 +573,11 @@ urlpatterns = patterns('', service_url('^messages/', include('group_messaging.urls')), service_url('^settings/', include('livesettings.urls')), - service_url('^api/v1/info/forum/$', views.api.api_forum_info, name='api_forum_info'), - service_url('^api/v1/info/users/$', views.api.api_users_info, name='api_users_info'), - service_url('^api/v1/info/users/(?P<user_id>\d+)/$', views.api.api_user_info, name='api_user_info'), - service_url('^api/v1/info/questions/$', views.api.api_latest_questions, name='api_latest_questions'), - service_url('^api/v1/info/questions/(?P<thread_id>\d+)/$', views.api.api_question_info, name='api_question_info'), + service_url('^api/v1/info/$', views.api_v1.info, name='api_v1_info'), + service_url('^api/v1/users/$', views.api_v1.users, name='api_v1_users'), + service_url('^api/v1/users/(?P<user_id>\d+)/$', views.api_v1.user, name='api_v1_user'), + service_url('^api/v1/questions/$', views.api_v1.questions, name='api_v1_questions'), + service_url('^api/v1/questions/(?P<question_id>\d+)/$', views.api_v1.question, name='api_v1_question'), ) if 'askbot.deps.django_authopenid' in settings.INSTALLED_APPS: diff --git a/askbot/views/__init__.py b/askbot/views/__init__.py index c55ede4b..481ca8ab 100644 --- a/askbot/views/__init__.py +++ b/askbot/views/__init__.py @@ -8,7 +8,7 @@ from askbot.views import users from askbot.views import meta from askbot.views import sharing from askbot.views import widgets -from askbot.views import api +from askbot.views import api_v1 from django.conf import settings if 'avatar' in settings.INSTALLED_APPS: from askbot.views import avatar_views diff --git a/askbot/views/api.py b/askbot/views/api.py deleted file mode 100644 index 601727d2..00000000 --- a/askbot/views/api.py +++ /dev/null @@ -1,202 +0,0 @@ -from django.core.paginator import Paginator, EmptyPage, InvalidPage -from django.shortcuts import get_object_or_404 -from django.http import HttpResponse, Http404 -from django.contrib.auth.models import User -from django.utils import simplejson -from django.db.models import Q -from django.core.urlresolvers import reverse - -from askbot import models -from askbot.conf import settings as askbot_settings -from askbot.search.state_manager import SearchState - -def api_forum_info(request): - ''' - Returns general data about the forum - ''' - data = {} - data['answers'] = models.Post.objects.get_answers().count() - data['questions'] = models.Post.objects.get_questions().count() - data['comments'] = models.Post.objects.get_comments().count() - data['users'] = User.objects.filter(is_active=True).count() - - if askbot_settings.GROUPS_ENABLED: - data['groups'] = models.Group.objects.exclude_personal().count() - else: - data['groups'] = 0 - - json_string = simplejson.dumps(data) - return HttpResponse(json_string, mimetype='application/json') - -def api_user_info(request, user_id): - ''' - Returns data about one user - ''' - user = get_object_or_404(User, pk=user_id) - - data = {} - data['username'] = user.username - data['reputation'] = user.reputation - data['questions'] = models.Post.objects.get_questions(user).count() - data['answers'] = models.Post.objects.get_answers(user).count() - #epoch time - data['date_joined'] = user.date_joined.strftime('%s') - data['last_seen'] = user.last_seen.strftime('%s') - - json_string = simplejson.dumps(data) - return HttpResponse(json_string, mimetype='application/json') - - -def api_users_info(request): - ''' - Returns data of the most active or latest users. - ''' - allowed_order_by = ('recent', 'oldest', 'reputation', 'username') - order_by = request.GET.get('order_by', 'reputation') - - try: - page = int(request.GET.get("page", '1')) - except ValueError: - page = 1 - - if order_by not in allowed_order_by: - raise Http404 - else: - if order_by == 'reputation': - users = models.User.objects.exclude(status = 'b').order_by('-reputation') - elif order_by == 'oldest': - users = models.User.objects.exclude(status = 'b').order_by('date_joined') - elif order_by == 'recent': - users = models.User.objects.exclude(status = 'b').order_by('-date_joined') - elif order_by == 'username': - users = models.User.objects.exclude(status = 'b').order_by('username') - else: - raise Exception("Order by method not allowed") - - - paginator = Paginator(users, 10) - - try: - user_objects = paginator.page(page) - except (EmptyPage, InvalidPage): - user_objects = paginator.page(paginator.num_pages) - - user_list = [] - #serializing to json - for user in user_objects: - user_dict = { - 'id': user.id, - 'username': user.username, - 'date_joined': user.date_joined.strftime('%s'), - 'reputation': user.reputation - } - user_list.append(dict.copy(user_dict)) - - response_dict = { - 'total_pages': paginator.num_pages, - 'count': paginator.count, - 'user_list': user_list - } - json_string = simplejson.dumps(response_dict) - - return HttpResponse(json_string, mimetype='application/json') - - -def api_question_info(request, question_id): - ''' - Gets a single question - ''' - post = get_object_or_404( - models.Post, id=question_id, - post_type='question', deleted=False - ) - question_url = '%s%s' % (askbot_settings.APP_URL, post.get_absolute_url()) - - response_dict = { - 'title': post.thread.title, - 'text': post.text, - 'username': post.author.username, - 'user_id': post.author.id, - 'url': question_url, - } - - json_string = simplejson.dumps(response_dict) - - return HttpResponse(json_string, mimetype='application/json') - - -def api_latest_questions(request): - """ - List of Questions, Tagged questions, and Unanswered questions. - matching search query or user selection - """ - try: - author_id = int(request.GET.get("author")) - except (TypeError, ValueError): - author_id = None - - try: - page = int(request.GET.get("page")) - except (TypeError, ValueError): - page = None - - search_state = SearchState( - scope = request.GET.get('scope', 'all'), - sort = request.GET.get('sort', 'activity-desc'), - query = request.GET.get("query", None), - tags = request.GET.get("tags", None), - author = author_id, - page = page, - user_logged_in=request.user.is_authenticated(), - ) - - page_size = int(askbot_settings.DEFAULT_QUESTIONS_PAGE_SIZE) - - qs, meta_data = models.Thread.objects.run_advanced_search( - request_user=request.user, search_state=search_state - ) - if meta_data['non_existing_tags']: - search_state = search_state.remove_tags(meta_data['non_existing_tags']) - - #exludes the question from groups - global_group = models.Group.objects.get_global_group() - qs = qs.exclude(~Q(groups__id=global_group.id)) - - paginator = Paginator(qs, page_size) - if paginator.num_pages < search_state.page: - search_state.page = 1 - page = paginator.page(search_state.page) - - question_list = list(page.object_list.values('title', 'view_count', - 'tagnames', - 'id', - 'last_activity_by__username', - 'answer_count')) - - #adds urls - for i, question in enumerate(question_list): - question['url'] = '%s%s' % (askbot_settings.APP_URL, - reverse('question', - kwargs={'id': question['id']}) - ) - question_list[i] = question - - page.object_list = list(page.object_list) # evaluate the queryset - - models.Thread.objects.precache_view_data_hack(threads=page.object_list) - - ajax_data = { - 'query_data': { - 'tags': search_state.tags, - 'sort_order': search_state.sort, - 'ask_query_string': search_state.ask_query_string(), - }, - 'question_count': paginator.count, - 'query_string': request.META.get('QUERY_STRING', ''), - 'page_size' : page_size, - 'non_existing_tags': meta_data['non_existing_tags'], - 'question_list': question_list - } - - return HttpResponse(simplejson.dumps(ajax_data), - mimetype = 'application/json') diff --git a/askbot/views/api_v1.py b/askbot/views/api_v1.py new file mode 100644 index 00000000..1141bd30 --- /dev/null +++ b/askbot/views/api_v1.py @@ -0,0 +1,199 @@ +from django.core.paginator import Paginator, EmptyPage, InvalidPage +from django.shortcuts import get_object_or_404 +from django.http import HttpResponse, Http404 +from django.contrib.auth.models import User +from django.utils import simplejson +from django.db.models import Q +from django.core.urlresolvers import reverse +from askbot import models +from askbot.conf import settings as askbot_settings +from askbot.search.state_manager import SearchState +from askbot.utils.html import site_url + +def get_user_data(user): + """get common data about the user""" + avatar_url = user.get_avatar_url() + if not ('gravatar.com' in avatar_url): + avatar_url = site_url(avatar_url) + + return { + 'id': user.id, + 'avatar': avatar_url, + 'name': user.username, + 'joined_at': user.date_joined.strftime('%s'), + 'last_seen_at': user.last_seen.strftime('%s'), + 'reputation': user.reputation, + } + +def get_question_data(thread): + """returns data dictionary for a given thread""" + datum = { + 'added_at': thread.added_at.strftime('%s'), + 'id': thread._question_post().id, + 'answer_count': thread.answer_count, + 'view_count': thread.view_count, + 'score': thread.score, + 'last_activity_at': thread.last_activity_at.strftime('%s'), + 'title': thread.title, + 'tags': thread.tagnames.strip().split(), + 'url': site_url(thread.get_absolute_url()), + } + datum['author'] = { + 'id': thread._question_post().author.id, + 'name': thread._question_post().author.username + } + datum['last_activity_by'] = { + 'id': thread.last_activity_by.id, + 'name': thread.last_activity_by.username + } + return datum + +def info(request): + ''' + Returns general data about the forum + ''' + data = {} + data['answers'] = models.Post.objects.get_answers().count() + data['questions'] = models.Post.objects.get_questions().count() + data['comments'] = models.Post.objects.get_comments().count() + data['users'] = User.objects.filter(is_active=True).count() + + if askbot_settings.GROUPS_ENABLED: + data['groups'] = models.Group.objects.exclude_personal().count() + else: + data['groups'] = 0 + + json_string = simplejson.dumps(data) + return HttpResponse(json_string, mimetype='application/json') + +def user(request, user_id): + ''' + Returns data about one user + ''' + user = get_object_or_404(User, pk=user_id) + data = get_user_data(user) + data['questions'] = models.Post.objects.get_questions(user).count() + data['answers'] = models.Post.objects.get_answers(user).count() + data['comments'] = models.Post.objects.filter(post_type='comment').count() + json_string = simplejson.dumps(data) + return HttpResponse(json_string, mimetype='application/json') + + +def users(request): + ''' + Returns data of the most active or latest users. + ''' + allowed_order_by = ('recent', 'oldest', 'reputation', 'username') + order_by = request.GET.get('sort', 'reputation') + + try: + page = int(request.GET.get("page", '1')) + except ValueError: + page = 1 + + if order_by not in allowed_order_by: + raise Http404 + else: + if order_by == 'reputation': + users = models.User.objects.exclude(status='b').order_by('-reputation') + elif order_by == 'oldest': + users = models.User.objects.exclude(status='b').order_by('date_joined') + elif order_by == 'recent': + users = models.User.objects.exclude(status='b').order_by('-date_joined') + elif order_by == 'username': + users = models.User.objects.exclude(status='b').order_by('username') + else: + raise Exception("Order by method not allowed") + + + paginator = Paginator(users, 10) + + try: + user_objects = paginator.page(page) + except (EmptyPage, InvalidPage): + user_objects = paginator.page(paginator.num_pages) + + user_list = [] + #serializing to json + for user in user_objects: + user_dict = get_user_data(user) + user_list.append(dict.copy(user_dict)) + + response_dict = { + 'pages': paginator.num_pages, + 'count': paginator.count, + 'users': user_list + } + json_string = simplejson.dumps(response_dict) + + return HttpResponse(json_string, mimetype='application/json') + + +def question(request, question_id): + ''' + Gets a single question + ''' + #we retrieve question by post id, b/c that's what is in the url, + #not thread id (currently) + post = get_object_or_404( + models.Post, id=question_id, + post_type='question', deleted=False + ) + datum = get_question_data(post.thread) + json_string = simplejson.dumps(datum) + return HttpResponse(json_string, mimetype='application/json') + + +def questions(request): + """ + List of Questions, Tagged questions, and Unanswered questions. + matching search query or user selection + """ + try: + author_id = int(request.GET.get("author")) + except (ValueError, TypeError): + author_id = None + + try: + page = int(request.GET.get("page")) + except (ValueError, TypeError): + page = None + + search_state = SearchState( + scope=request.GET.get('scope', 'all'), + sort=request.GET.get('sort', 'activity-desc'), + query=request.GET.get('query', None), + tags=request.GET.get('tags', None), + author=author_id, + page=page, + user_logged_in=request.user.is_authenticated(), + ) + + qs, meta_data = models.Thread.objects.run_advanced_search( + request_user=request.user, search_state=search_state + ) + if meta_data['non_existing_tags']: + search_state = search_state.remove_tags(meta_data['non_existing_tags']) + + #exludes the question from groups + #global_group = models.Group.objects.get_global_group() + #qs = qs.exclude(~Q(groups__id=global_group.id)) + + page_size = askbot_settings.DEFAULT_QUESTIONS_PAGE_SIZE + paginator = Paginator(qs, page_size) + if paginator.num_pages < search_state.page: + search_state.page = 1 + page = paginator.page(search_state.page) + + question_list = list() + for thread in page.object_list: + datum = get_question_data(thread) + question_list.append(datum) + + ajax_data = { + 'count': paginator.count, + 'pages' : paginator.num_pages, + 'questions': question_list + } + response_data = simplejson.dumps(ajax_data) + return HttpResponse(response_data, mimetype='application/json') |