1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-17 07:41:26 +00:00

[api] Add file comment endpoints

This commit is contained in:
zhengxie
2016-05-01 17:41:18 +08:00
committed by llj
parent ee99d7aad8
commit e8579d0742
11 changed files with 432 additions and 2 deletions

View File

@@ -0,0 +1,51 @@
import logging
from rest_framework import status
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from seaserv import seafile_api
from pysearpc import SearpcError
from seahub.api2.authentication import TokenAuthentication
from seahub.api2.permissions import IsRepoAccessible
from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error
from seahub.base.models import FileComment
logger = logging.getLogger(__name__)
class FileCommentView(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated, IsRepoAccessible)
throttle_classes = (UserRateThrottle, )
def get(self, request, repo_id, pk, format=None):
"""Get a comment.
"""
try:
o = FileComment.objects.get(pk=pk)
except FileComment.DoesNotExist:
return api_error(status.HTTP_400_BAD_REQUEST, 'Wrong comment id')
return Response(o.to_dict())
def delete(self, request, repo_id, pk, format=None):
"""Delete a comment, only comment author or repo owner can perform
this op.
"""
try:
o = FileComment.objects.get(pk=pk)
except FileComment.DoesNotExist:
return api_error(status.HTTP_400_BAD_REQUEST, 'Wrong comment id')
username = request.user.username
if username != o.author and \
not seafile_api.is_repo_owner(username, repo_id):
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
o.delete()
return Response(status=204)

View File

@@ -0,0 +1,64 @@
import logging
from rest_framework import status
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from seaserv import seafile_api
from pysearpc import SearpcError
from seahub.api2.authentication import TokenAuthentication
from seahub.api2.permissions import IsRepoAccessible
from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error
from seahub.base.models import FileComment
logger = logging.getLogger(__name__)
class FileCommentsView(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated, IsRepoAccessible)
throttle_classes = (UserRateThrottle, )
def get(self, request, repo_id, format=None):
"""List all comments of a file.
"""
path = request.GET.get('p', '/').rstrip('/')
if not path:
return api_error(status.HTTP_400_BAD_REQUEST, 'Wrong path.')
comments = []
for o in FileComment.objects.get_by_file_path(repo_id, path):
comments.append(o.to_dict())
return Response({
"comments": comments,
})
def post(self, request, repo_id, format=None):
"""Post a comments of a file.
"""
path = request.GET.get('p', '/').rstrip('/')
if not path:
return api_error(status.HTTP_400_BAD_REQUEST, 'Wrong path.')
try:
obj_id = seafile_api.get_file_id_by_path(repo_id,
path)
except SearpcError as e:
logger.error(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR,
'Internal error.')
if not obj_id:
return api_error(status.HTTP_404_NOT_FOUND, 'File not found.')
comment = request.data.get('comment', '')
if not comment:
return api_error(status.HTTP_400_BAD_REQUEST, 'Comment can not be empty.')
username = request.user.username
o = FileComment.objects.add_by_file_path(
repo_id=repo_id, file_path=path, author=username, comment=comment)
return Response(o.to_dict(), status=201)

View File

@@ -0,0 +1,50 @@
import logging
from rest_framework import status
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from seaserv import seafile_api
from pysearpc import SearpcError
from seahub.api2.authentication import TokenAuthentication
from seahub.api2.permissions import IsRepoAccessible
from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error
from seahub.base.models import FileComment
logger = logging.getLogger(__name__)
class FileCommentsCounts(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated, IsRepoAccessible)
throttle_classes = (UserRateThrottle, )
def get(self, request, repo_id, format=None):
"""Count all comments of all file under certain parent dir.
"""
path = request.GET.get('p', '/')
if not path:
return api_error(status.HTTP_400_BAD_REQUEST, 'Wrong path.')
try:
obj_id = seafile_api.get_dir_id_by_path(repo_id,
path)
except SearpcError as e:
logger.error(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR,
'Internal error.')
if not obj_id:
return api_error(status.HTTP_404_NOT_FOUND, 'Parent dir not found.')
d = {}
for o in FileComment.objects.get_by_parent_path(repo_id, path):
d[o.item_name] = d.get(o.item_name, 0) + 1
ret = []
for k, v in d.iteritems():
ret.append({k: v})
return Response(ret)

View File

@@ -7,6 +7,9 @@ from .endpoints.dir_shared_items import DirSharedItemsEndpoint
from .endpoints.account import Account from .endpoints.account import Account
from .endpoints.shared_upload_links import SharedUploadLinksView from .endpoints.shared_upload_links import SharedUploadLinksView
from .endpoints.be_shared_repo import BeSharedReposView from .endpoints.be_shared_repo import BeSharedReposView
from .endpoints.file_comment import FileCommentView
from .endpoints.file_comments import FileCommentsView
from .endpoints.file_comments_counts import FileCommentsCounts
from .endpoints.search_user import SearchUser from .endpoints.search_user import SearchUser
from .endpoints.group_discussions import GroupDiscussions from .endpoints.group_discussions import GroupDiscussions
from .endpoints.group_discussion import GroupDiscussion from .endpoints.group_discussion import GroupDiscussion
@@ -49,6 +52,9 @@ urlpatterns = patterns('',
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/owa-file/$', OwaFileView.as_view(), name='api2-owa-file-view'), url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/owa-file/$', OwaFileView.as_view(), name='api2-owa-file-view'),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/file/$', FileView.as_view(), name='FileView'), url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/file/$', FileView.as_view(), name='FileView'),
url(r'^repos/(?P<repo_id>[-0-9a-f]{36})/files/(?P<file_id>[0-9a-f]{40})/blks/(?P<block_id>[0-9a-f]{40})/download-link/$', FileBlockDownloadLinkView.as_view()), url(r'^repos/(?P<repo_id>[-0-9a-f]{36})/files/(?P<file_id>[0-9a-f]{40})/blks/(?P<block_id>[0-9a-f]{40})/download-link/$', FileBlockDownloadLinkView.as_view()),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/file/comments/$', FileCommentsView.as_view(), name='api2-file-comments'),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/file/comments/counts/$', FileCommentsCounts.as_view(), name='api2-file-comments-counts'),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/file/comments/(?P<pk>\d+)/$', FileCommentView.as_view(), name='api2-file-comment'),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/file/detail/$', FileDetailView.as_view()), url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/file/detail/$', FileDetailView.as_view()),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/file/history/$', FileHistory.as_view()), url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/file/history/$', FileHistory.as_view()),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/file/revision/$', FileRevision.as_view()), url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/file/revision/$', FileRevision.as_view()),

View File

@@ -1,4 +1,6 @@
import os
import datetime import datetime
import hashlib
import logging import logging
import json import json
from django.db import models, IntegrityError from django.db import models, IntegrityError
@@ -10,6 +12,7 @@ from seaserv import seafile_api
from seahub.auth.signals import user_logged_in from seahub.auth.signals import user_logged_in
from seahub.group.models import GroupMessage from seahub.group.models import GroupMessage
from seahub.utils import calc_file_path_hash, within_time_range from seahub.utils import calc_file_path_hash, within_time_range
from seahub.utils.timeutils import datetime_to_isoformat_timestr
from fields import LowerCaseCharField from fields import LowerCaseCharField
@@ -28,11 +31,89 @@ class FileDiscuss(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.path_hash: if not self.path_hash:
self.path_hash = calc_file_path_hash(self.path) self.path_hash = calc_file_path_hash(self.path)
super(FileDiscuss, self).save(*args, **kwargs) super(FileDiscuss, self).save(*args, **kwargs)
class FileCommentManager(models.Manager):
def add(self, repo_id, parent_path, item_name, author, comment):
c = self.model(repo_id=repo_id, parent_path=parent_path,
item_name=item_name, author=author, comment=comment)
c.save(using=self._db)
return c
def add_by_file_path(self, repo_id, file_path, author, comment):
file_path = self.model.normalize_path(file_path)
parent_path = os.path.dirname(file_path)
item_name = os.path.basename(file_path)
return self.add(repo_id, parent_path, item_name, author, comment)
def get_by_file_path(self, repo_id, file_path):
parent_path = os.path.dirname(file_path)
item_name = os.path.basename(file_path)
repo_id_parent_path_md5 = self.model.md5_repo_id_parent_path(
repo_id, parent_path)
objs = super(FileCommentManager, self).filter(
repo_id_parent_path_md5=repo_id_parent_path_md5,
item_name=item_name)
return objs
def get_by_parent_path(self, repo_id, parent_path):
repo_id_parent_path_md5 = self.model.md5_repo_id_parent_path(
repo_id, parent_path)
objs = super(FileCommentManager, self).filter(
repo_id_parent_path_md5=repo_id_parent_path_md5)
return objs
class FileComment(models.Model):
"""
Model used to record file comments.
"""
repo_id = models.CharField(max_length=36, db_index=True)
parent_path = models.TextField()
repo_id_parent_path_md5 = models.CharField(max_length=100, db_index=True)
item_name = models.TextField()
author = LowerCaseCharField(max_length=255, db_index=True)
comment = models.TextField()
created_at = models.DateTimeField(default=timezone.now)
updated_at = models.DateTimeField(default=timezone.now)
objects = FileCommentManager()
@classmethod
def md5_repo_id_parent_path(cls, repo_id, parent_path):
return hashlib.md5(repo_id + parent_path.rstrip('/')).hexdigest()
@classmethod
def normalize_path(self, path):
return path.rstrip('/') if path != '/' else '/'
def save(self, *args, **kwargs):
self.parent_path = self.normalize_path(self.parent_path)
if not self.repo_id_parent_path_md5:
self.repo_id_parent_path_md5 = self.md5_repo_id_parent_path(
self.repo_id, self.parent_path)
super(FileComment, self).save(*args, **kwargs)
def to_dict(self):
o = self
return {
'id': o.pk,
'repo_id': o.repo_id,
'parent_path': o.parent_path,
'item_name': o.item_name,
'author': o.author,
'comment': o.comment,
'created_at': datetime_to_isoformat_timestr(o.created_at),
}
########## starred files ########## starred files
class StarredFile(object): class StarredFile(object):
def format_path(self): def format_path(self):
@@ -206,7 +287,7 @@ class InnerPubMsgReply(models.Model):
from_email = models.EmailField() from_email = models.EmailField()
message = models.CharField(max_length=150) message = models.CharField(max_length=150)
timestamp = models.DateTimeField(default=datetime.datetime.now) timestamp = models.DateTimeField(default=datetime.datetime.now)
##############################
class DeviceToken(models.Model): class DeviceToken(models.Model):
""" """

View File

@@ -0,0 +1,42 @@
import json
from django.core.urlresolvers import reverse
from seahub.base.models import FileComment
from seahub.test_utils import BaseTestCase
class FileCommentTest(BaseTestCase):
def setUp(self):
self.login_as(self.user)
o = FileComment.objects.add_by_file_path(repo_id=self.repo.id,
file_path=self.file,
author=self.user.username,
comment='test comment')
self.endpoint = reverse('api2-file-comment', args=[self.repo.id, o.pk]) + '?p=' + self.file
def tearDown(self):
self.remove_repo()
def test_can_get(self):
resp = self.client.get(self.endpoint)
self.assertEqual(200, resp.status_code)
json_resp = json.loads(resp.content)
assert json_resp['parent_path'] == '/'
assert json_resp['item_name'] == 'test.txt'
def test_can_delete(self):
assert len(FileComment.objects.all()) == 1
resp = self.client.delete(self.endpoint)
self.assertEqual(204, resp.status_code)
assert len(FileComment.objects.all()) == 0
def test_invalid_user_can_not_delete(self):
self.logout()
self.login_as(self.admin)
assert len(FileComment.objects.all()) == 1
resp = self.client.delete(self.endpoint)
self.assertEqual(403, resp.status_code)
assert len(FileComment.objects.all()) == 1

View File

@@ -0,0 +1,47 @@
import json
from django.core.urlresolvers import reverse
from seahub.base.models import FileComment
from seahub.test_utils import BaseTestCase
class FileCommentsTest(BaseTestCase):
def setUp(self):
self.login_as(self.user)
self.endpoint = reverse('api2-file-comments', args=[self.repo.id]) + '?p=' + self.file
def tearDown(self):
self.remove_repo()
def test_can_list(self):
o = FileComment.objects.add_by_file_path(repo_id=self.repo.id,
file_path=self.file,
author=self.user.username,
comment='test comment')
resp = self.client.get(self.endpoint)
self.assertEqual(200, resp.status_code)
json_resp = json.loads(resp.content)
assert len(json_resp['comments']) == 1
assert json_resp['comments'][0]['comment'] == o.comment
def test_can_post(self):
resp = self.client.post(self.endpoint, {
'comment': 'new comment'
})
self.assertEqual(201, resp.status_code)
json_resp = json.loads(resp.content)
assert json_resp['comment'] == 'new comment'
def test_invalid_user(self):
self.logout()
self.login_as(self.admin)
resp = self.client.get(self.endpoint)
self.assertEqual(403, resp.status_code)
resp = self.client.post(self.endpoint, {
'comment': 'new comment'
})
self.assertEqual(403, resp.status_code)

View File

@@ -0,0 +1,46 @@
import json
from django.core.urlresolvers import reverse
from seahub.base.models import FileComment
from seahub.test_utils import BaseTestCase
class FileCommentsCountsTest(BaseTestCase):
def setUp(self):
self.login_as(self.user)
self.endpoint = reverse('api2-file-comments-counts', args=[self.repo.id]) + '?p=/'
self.file2 = self.create_file(repo_id=self.repo.id, parent_dir='/',
filename='test2.txt',
username=self.user.username)
def tearDown(self):
self.remove_repo()
def test_can_get(self):
FileComment.objects.add_by_file_path(repo_id=self.repo.id,
file_path=self.file,
author=self.user.username,
comment='test comment')
FileComment.objects.add_by_file_path(repo_id=self.repo.id,
file_path=self.file,
author=self.user.username,
comment='reply test comment')
FileComment.objects.add_by_file_path(repo_id=self.repo.id,
file_path=self.file2,
author=self.user.username,
comment='test comment on other file')
resp = self.client.get(self.endpoint)
self.assertEqual(200, resp.status_code)
json_resp = json.loads(resp.content)
assert len(json_resp) == 2
for d in json_resp:
if d.keys()[0] == 'test.txt':
assert d['test.txt'] == 2
if d.keys()[0] == 'test2.txt':
assert d['test2.txt'] == 1

View File

View File

@@ -0,0 +1,43 @@
from seahub.base.models import FileComment
from seahub.test_utils import BaseTestCase
class FileCommentManagerTest(BaseTestCase):
def test_can_add(self):
assert len(FileComment.objects.all()) == 0
o = FileComment.objects.add(repo_id='xxx', parent_path='/foo/bar',
item_name='test.txt',
author=self.user.username,
comment='test comment')
assert o.parent_path == '/foo/bar'
assert len(FileComment.objects.all()) == 1
def test_get_by_file_path(self):
o1 = FileComment.objects.add(repo_id='xxx', parent_path='/foo/bar/',
item_name='test.txt',
author=self.user.username,
comment='test comment 1')
o2 = FileComment.objects.add(repo_id='xxx', parent_path='/foo/bar/',
item_name='test.txt',
author=self.user.username,
comment='test comment 2')
assert len(FileComment.objects.get_by_file_path('xxx', '/foo/bar/test.txt')) == 2
class FileCommentTest(BaseTestCase):
def test_normalize_path(self):
o = FileComment.objects.add(repo_id='xxx', parent_path='/foo/bar/',
item_name='test.txt',
author=self.user.username,
comment='test comment')
assert o.parent_path == '/foo/bar'
def test_can_save(self):
assert len(FileComment.objects.all()) == 0
FileComment(repo_id='xxx', parent_path='/foo/bar/',
item_name='test.txt', author=self.user.username,
comment='test comment').save()
assert len(FileComment.objects.all()) == 1

View File