1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-07-08 20:54:47 +00:00

file participants api-v2.1

This commit is contained in:
sniper-py 2019-06-10 16:08:45 +08:00 committed by Michael An
parent 0b7a3be295
commit ae543df9b7
11 changed files with 370 additions and 31 deletions

View File

View File

@ -0,0 +1,64 @@
# Copyright (c) 2012-2019 Seafile Ltd.
import os
import logging
from django.db import models
from seahub.tags.models import FileUUIDMap
from seahub.base.fields import LowerCaseCharField
# Get an instance of a logger
logger = logging.getLogger(__name__)
class FileParticipantManager(models.Manager):
def _get_file_uuid_map(self, repo_id, file_path):
parent_path = os.path.dirname(file_path)
item_name = os.path.basename(file_path)
file_uuid_map = FileUUIDMap.objects.get_or_create_fileuuidmap(
repo_id, parent_path, item_name, False)
return file_uuid_map
def add_by_file_path_and_username(self, repo_id, file_path, username):
uuid = self._get_file_uuid_map(repo_id, file_path)
if self.filter(uuid=uuid, username=username).exists():
return self.filter(uuid=uuid, username=username)[0]
obj = self.model(uuid=uuid, username=username)
obj.save(using=self._db)
return obj
def get_by_file_path_and_username(self, repo_id, file_path, username):
uuid = self._get_file_uuid_map(repo_id, file_path)
try:
obj = self.get(uuid=uuid, username=username)
return obj
except self.model.DoesNotExist:
return None
def delete_by_file_path_and_username(self, repo_id, file_path, username):
uuid = self._get_file_uuid_map(repo_id, file_path)
self.filter(uuid=uuid, username=username).delete()
def get_by_file_path(self, repo_id, file_path):
uuid = self._get_file_uuid_map(repo_id, file_path)
objs = self.filter(uuid=uuid)
return objs
class FileParticipant(models.Model):
"""
Model used to record file participants.
"""
uuid = models.ForeignKey(FileUUIDMap, on_delete=models.CASCADE)
username = LowerCaseCharField(max_length=255)
objects = FileParticipantManager()
class Meta:
"""Meta data"""
unique_together = ('uuid', 'username')

View File

@ -0,0 +1,12 @@
from .models import FileParticipant
def list_file_participants_username(repo_id, path):
""" return participants username list
"""
username_list = []
file_participant_queryset = FileParticipant.objects.get_by_file_path(repo_id, path)
for participant in file_participant_queryset:
username_list.append(participant.username)
return username_list

View File

@ -0,0 +1,144 @@
# Copyright (c) 2012-2019 Seafile Ltd.
# -*- coding: utf-8 -*-
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 seahub.api2.authentication import TokenAuthentication
from seahub.api2.permissions import IsRepoAccessible
from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error, get_user_common_info
from .models import FileParticipant
from seahub.utils import normalize_file_path, is_valid_username
from seahub.views import check_folder_permission
from pysearpc import SearpcError
from seahub.base.accounts import User
logger = logging.getLogger(__name__)
class FileParticipantsView(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated, IsRepoAccessible)
throttle_classes = (UserRateThrottle,)
def get(self, request, repo_id, format=None):
"""List all participants of a file.
"""
# argument check
path = request.GET.get('path', '/').rstrip('/')
if not path:
return api_error(status.HTTP_400_BAD_REQUEST, 'Invalid path.')
path = normalize_file_path(path)
# resource check
try:
file_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 file_id:
return api_error(status.HTTP_404_NOT_FOUND, 'File not found.')
# permission check
if not check_folder_permission(request, repo_id, '/'):
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
# main
participant_list = []
participant_queryset = FileParticipant.objects.get_by_file_path(repo_id, path)
for participant in participant_queryset:
participant_info = get_user_common_info(participant.username)
participant_list.append(participant_info)
return Response({'participant_list': participant_list})
def post(self, request, repo_id, format=None):
"""Post a participant of a file.
"""
# argument check
path = request.GET.get('path', '/').rstrip('/')
if not path:
return api_error(status.HTTP_400_BAD_REQUEST, 'Invalid path.')
path = normalize_file_path(path)
email = request.data.get('email', '').lower()
if not email:
email = request.user.username
if not is_valid_username(email):
return api_error(status.HTTP_400_BAD_REQUEST, 'Invalid email.')
# resource check
try:
file_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 file_id:
return api_error(status.HTTP_404_NOT_FOUND, 'File not found.')
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
return api_error(status.HTTP_404_NOT_FOUND, 'User %s not found.' % email)
# permission check
if not check_folder_permission(request, repo_id, '/'):
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
request.user = user
if not check_folder_permission(request, repo_id, '/'):
return api_error(status.HTTP_403_FORBIDDEN, 'User %s permission denied.' % email)
# main
if FileParticipant.objects.get_by_file_path_and_username(repo_id, path, email):
return api_error(status.HTTP_400_BAD_REQUEST, 'Participant %s already exists.' % email)
FileParticipant.objects.add_by_file_path_and_username(repo_id, path, email)
participant = get_user_common_info(email)
return Response(participant, status=201)
class FileParticipantView(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated, IsRepoAccessible)
throttle_classes = (UserRateThrottle,)
def delete(self, request, repo_id, format=None):
"""Delete a participant
"""
# argument check
path = request.GET.get('path', '/').rstrip('/')
if not path:
return api_error(status.HTTP_400_BAD_REQUEST, 'Invalid path.')
path = normalize_file_path(path)
email = request.data.get('email', '').lower()
if not email or not is_valid_username(email):
return api_error(status.HTTP_400_BAD_REQUEST, 'Invalid email.')
# resource check
try:
file_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 file_id:
return api_error(status.HTTP_404_NOT_FOUND, 'File not found.')
# permission check
if not check_folder_permission(request, repo_id, '/'):
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
# main
FileParticipant.objects.delete_by_file_path_and_username(repo_id, path, email)
return Response({'success': True})

View File

@ -25,6 +25,7 @@ from seahub.utils import normalize_cache_key
from seahub.utils.timeutils import datetime_to_isoformat_timestr
from seahub.constants import HASH_URLS
from seahub.drafts.models import DraftReviewer
from seahub.file_participants.utils import list_file_participants_username
# Get an instance of a logger
logger = logging.getLogger(__name__)
@ -979,14 +980,15 @@ def add_user_to_group_cb(sender, **kwargs):
@receiver(comment_file_successful)
def comment_file_successful_cb(sender, **kwargs):
""" send notification to file participants
"""
repo = kwargs['repo']
repo_owner = kwargs['repo_owner']
file_path = kwargs['file_path']
comment = kwargs['comment']
author = kwargs['author']
notify_users = get_repo_shared_users(repo.id, repo_owner)
notify_users.append(repo_owner)
notify_users = list_file_participants_username(repo.id, file_path)
notify_users = [x for x in notify_users if x != author]
for u in notify_users:
detail = file_comment_msg_to_json(repo.id, file_path, author, comment)

View File

@ -259,6 +259,7 @@ INSTALLED_APPS = (
'seahub.related_files',
'seahub.work_weixin',
'seahub.dtable',
'seahub.file_participants',
)
# Enable or disable view File Scan

View File

@ -143,6 +143,7 @@ from seahub.api2.endpoints.admin.file_scan_records import AdminFileScanRecords
from seahub.api2.endpoints.admin.notifications import AdminNotificationsView
from seahub.api2.endpoints.admin.work_weixin import AdminWorkWeixinDepartments, \
AdminWorkWeixinDepartmentMembers, AdminWorkWeixinUsersBatch
from seahub.file_participants.views import FileParticipantsView, FileParticipantView
urlpatterns = [
url(r'^accounts/', include('seahub.base.registration_urls')),
@ -353,6 +354,8 @@ urlpatterns = [
url(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/file-tags/$', RepoFileTagsView.as_view(), name='api-v2.1-file-tags'),
url(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/file-tags/(?P<file_tag_id>\d+)/$', RepoFileTagView.as_view(), name='api-v2.1-file-tag'),
url(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/tagged-files/(?P<repo_tag_id>\d+)/$', TaggedFilesView.as_view(), name='api-v2.1-tagged-files'),
url(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/file/participants/$', FileParticipantsView.as_view(), name='api-v2.1-file-participants'),
url(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/file/participant/$', FileParticipantView.as_view(), name='api-v2.1-file-participant'),
# user::related-files
url(r'^api/v2.1/related-files/$', RelatedFilesView.as_view(), name='api-v2.1-related-files'),

View File

@ -7,6 +7,7 @@ from seaserv import seafile_api, ccnet_api
from seahub.base.models import FileComment
from seahub.notifications.models import UserNotification
from seahub.test_utils import BaseTestCase
from seahub.file_participants.models import FileParticipant
class FileCommentsTest(BaseTestCase):
def setUp(self):
@ -86,12 +87,13 @@ class FileCommentsTest(BaseTestCase):
})
self.assertEqual(403, resp.status_code)
def test_can_notify_others(self):
def test_can_notify_participant(self):
assert len(UserNotification.objects.all()) == 0
username = self.user.username
seafile_api.share_repo(self.repo.id, username,
self.admin.username, 'rw')
# share repo and add participant
seafile_api.share_repo(self.repo.id, self.user.username, self.admin.username, 'rw')
FileParticipant.objects.add_by_file_path_and_username(
repo_id=self.repo.id, file_path=self.file, username=self.admin.username)
resp = self.client.post(self.endpoint, {
'comment': 'new comment'
@ -100,28 +102,3 @@ class FileCommentsTest(BaseTestCase):
assert len(UserNotification.objects.all()) == 1
assert UserNotification.objects.all()[0].to_user == self.admin.username
def test_can_notify_others_including_group(self):
self.logout()
self.login_as(self.tmp_user)
assert len(UserNotification.objects.all()) == 0
# share repo to tmp_user
username = self.user.username
seafile_api.share_repo(self.repo.id, username,
self.tmp_user.username, 'rw')
# share repo to group(owner, admin)
ccnet_api.group_add_member(self.group.id, username,
self.admin.username)
seafile_api.set_group_repo(self.repo.id, self.group.id,
username, 'rw')
# tmp_user comment a file
resp = self.client.post(self.endpoint, {
'comment': 'new comment'
})
self.assertEqual(201, resp.status_code)
assert len(UserNotification.objects.all()) == 2

View File

@ -0,0 +1,48 @@
import json
from seaserv import seafile_api
from django.core.urlresolvers import reverse
from seahub.file_participants.models import FileParticipant
from seahub.test_utils import BaseTestCase
from tests.common.utils import randstring
class FileParticipantTest(BaseTestCase):
def setUp(self):
self.tmp_user = self.create_user()
self.login_as(self.user)
self.url = reverse('api-v2.1-file-participant', args=[self.repo.id]) + '?path=' + self.file
# share repo and add participant
seafile_api.share_repo(self.repo.id, self.user.username, self.tmp_user.username, 'rw')
FileParticipant.objects.add_by_file_path_and_username(
repo_id=self.repo.id, file_path=self.file, username=self.tmp_user.username)
def tearDown(self):
self.remove_repo()
self.remove_user(self.tmp_user.email)
def test_can_delete(self):
data = 'email=' + self.tmp_user.username
resp = self.client.delete(self.url, data, 'application/x-www-form-urlencoded')
self.assertEqual(200, resp.status_code)
json_resp = json.loads(resp.content)
assert json_resp['success']
def test_can_not_delete_by_not_exists_path(self):
invalid_path = randstring(5)
data = 'email=' + self.tmp_user.username
resp = self.client.delete(self.url + invalid_path, data, 'application/x-www-form-urlencoded')
self.assertEqual(404, resp.status_code)
def test_can_not_delete_by_invalid_user_permission(self):
self.logout()
self.login_as(self.admin)
data = 'email=' + self.tmp_user.username
resp = self.client.delete(self.url, data, 'application/x-www-form-urlencoded')
self.assertEqual(403, resp.status_code)

View File

@ -0,0 +1,88 @@
import json
from seaserv import seafile_api
from django.core.urlresolvers import reverse
from seahub.file_participants.models import FileParticipant
from seahub.test_utils import BaseTestCase
from tests.common.utils import randstring
class FileParticipantsTest(BaseTestCase):
def setUp(self):
self.tmp_user = self.create_user()
self.login_as(self.user)
self.url = reverse('api-v2.1-file-participants', args=[self.repo.id]) + '?path=' + self.file
# share repo and add participant
seafile_api.share_repo(self.repo.id, self.user.username, self.tmp_user.username, 'rw')
FileParticipant.objects.add_by_file_path_and_username(
repo_id=self.repo.id, file_path=self.file, username=self.user.username)
def tearDown(self):
self.remove_repo()
self.remove_user(self.tmp_user.email)
def test_can_list(self):
resp = self.client.get(self.url)
self.assertEqual(200, resp.status_code)
json_resp = json.loads(resp.content)
assert json_resp['participant_list']
assert json_resp['participant_list'][0]
assert json_resp['participant_list'][0]['email'] == self.user.username
assert json_resp['participant_list'][0]['avatar_url']
assert json_resp['participant_list'][0]['contact_email']
assert json_resp['participant_list'][0]['name']
def test_can_not_list_by_not_exists_path(self):
invalid_path = randstring(5)
resp = self.client.get(self.url + invalid_path)
self.assertEqual(404, resp.status_code)
def test_can_not_list_by_invalid_user_permission(self):
self.logout()
self.login_as(self.admin)
resp = self.client.get(self.url)
self.assertEqual(403, resp.status_code)
def test_can_post(self):
resp = self.client.post(self.url, {
'email': self.tmp_user.username
})
self.assertEqual(201, resp.status_code)
json_resp = json.loads(resp.content)
assert json_resp['email'] == self.tmp_user.username
assert json_resp['avatar_url']
assert json_resp['contact_email']
assert json_resp['name']
def test_can_not_post_by_not_exists_path(self):
invalid_path = randstring(5)
resp = self.client.post(self.url + invalid_path, {
'email': self.tmp_user.username
})
self.assertEqual(404, resp.status_code)
def test_can_not_post_by_invalid_user_permission(self):
self.logout()
self.login_as(self.admin)
resp = self.client.post(self.url, {
'email': self.tmp_user.username
})
self.assertEqual(403, resp.status_code)
def test_can_not_post_by_not_exists_user(self):
invalid_email = randstring(5) + '@seafile.com'
resp = self.client.post(self.url, {
'email': invalid_email
})
self.assertEqual(404, resp.status_code)
def test_can_not_post_by_invalid_email_permission(self):
resp = self.client.post(self.url, {
'email': self.admin.username
})
self.assertEqual(403, resp.status_code)