mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-04 08:28:11 +00:00
update revert file/dir
This commit is contained in:
@@ -110,10 +110,12 @@ class DirView(APIView):
|
||||
return get_dir_entrys_by_id(request, repo, path, dir_id, request_type)
|
||||
|
||||
def post(self, request, repo_id, format=None):
|
||||
""" Create, rename dir.
|
||||
""" Create, rename, revert dir.
|
||||
|
||||
Permission checking:
|
||||
1. user with 'rw' permission.
|
||||
1. create: user with 'rw' permission for current dir's parent dir;
|
||||
2. rename: user with 'rw' permission for current dir;
|
||||
3. revert: user with 'rw' permission for current dir's parent dir;
|
||||
"""
|
||||
|
||||
# argument check
|
||||
@@ -123,7 +125,7 @@ class DirView(APIView):
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
if path == '/':
|
||||
error_msg = 'Can not make or rename root dir.'
|
||||
error_msg = 'Can not operate root dir.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
operation = request.data.get('operation', None)
|
||||
@@ -132,8 +134,8 @@ class DirView(APIView):
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
operation = operation.lower()
|
||||
if operation not in ('mkdir', 'rename'):
|
||||
error_msg = "operation can only be 'mkdir', 'rename'."
|
||||
if operation not in ('mkdir', 'rename', 'revert'):
|
||||
error_msg = "operation can only be 'mkdir', 'rename' or 'revert'."
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
# resource check
|
||||
@@ -211,6 +213,32 @@ class DirView(APIView):
|
||||
error_msg = 'Internal Server Error'
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
|
||||
if operation == 'revert':
|
||||
commit_id = request.data.get('commit_id', None)
|
||||
if not commit_id:
|
||||
error_msg = 'commit_id invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
if seafile_api.get_dir_id_by_path(repo_id, path):
|
||||
# dir exists in repo
|
||||
if check_folder_permission(request, repo_id, path) != 'rw':
|
||||
error_msg = 'Permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
else:
|
||||
# dir NOT exists in repo
|
||||
if check_folder_permission(request, repo_id, '/') != 'rw':
|
||||
error_msg = 'Permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
try:
|
||||
seafile_api.revert_dir(repo_id, commit_id, path, username)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
|
||||
return Response({'success': True})
|
||||
|
||||
def delete(self, request, repo_id, format=None):
|
||||
""" Delete dir.
|
||||
|
||||
|
@@ -9,6 +9,8 @@ from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework import status
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from seahub.api2.throttling import UserRateThrottle
|
||||
from seahub.api2.authentication import TokenAuthentication
|
||||
from seahub.api2.utils import api_error
|
||||
@@ -92,13 +94,14 @@ class FileView(APIView):
|
||||
return Response(file_info)
|
||||
|
||||
def post(self, request, repo_id, format=None):
|
||||
""" Create, rename, move, copy file
|
||||
""" Create, rename, move, copy, revert file
|
||||
|
||||
Permission checking:
|
||||
1. create: user with 'rw' permission for current parent dir;
|
||||
2. rename: user with 'rw' permission for current file;
|
||||
3. move : user with 'rw' permission for current file, 'rw' permission for dst parent dir;
|
||||
4. copy : user with 'r' permission for current file, 'rw' permission for dst parent dir;
|
||||
4. revert: user with 'rw' permission for current file's parent dir;
|
||||
"""
|
||||
|
||||
# argument check
|
||||
@@ -113,8 +116,8 @@ class FileView(APIView):
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
operation = operation.lower()
|
||||
if operation not in ('create', 'rename', 'move', 'copy'):
|
||||
error_msg = "operation can only be 'create', 'rename', 'move' or 'copy'."
|
||||
if operation not in ('create', 'rename', 'move', 'copy', 'revert'):
|
||||
error_msg = "operation can only be 'create', 'rename', 'move', 'copy' or 'revert'."
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
# resource check
|
||||
@@ -346,6 +349,42 @@ class FileView(APIView):
|
||||
dst_file_info = self.get_file_info(username, dst_repo_id, dst_file_path)
|
||||
return Response(dst_file_info)
|
||||
|
||||
if operation == 'revert':
|
||||
commit_id = request.data.get('commit_id', None)
|
||||
if not commit_id:
|
||||
error_msg = 'commit_id invalid.'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
if seafile_api.get_file_id_by_path(repo_id, path):
|
||||
# file exists in repo
|
||||
if check_folder_permission(request, repo_id, parent_dir) != 'rw':
|
||||
error_msg = 'Permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
is_locked, locked_by_me = check_file_lock(repo_id, path, username)
|
||||
if (is_locked, locked_by_me) == (None, None):
|
||||
error_msg = _("Check file lock error")
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
|
||||
if is_locked and not locked_by_me:
|
||||
error_msg = _("File is locked")
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
else:
|
||||
# file NOT exists in repo
|
||||
if check_folder_permission(request, repo_id, '/') != 'rw':
|
||||
error_msg = 'Permission denied.'
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
try:
|
||||
seafile_api.revert_file(repo_id, commit_id, path, username)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
|
||||
return Response({'success': True})
|
||||
|
||||
def put(self, request, repo_id, format=None):
|
||||
""" Currently only for lock and unlock file operation.
|
||||
|
||||
|
@@ -70,7 +70,7 @@
|
||||
<td>
|
||||
{% if commit.id != repo.head_cmmt_id %}
|
||||
{% if can_revert_file %}
|
||||
<a href="#" data-url="{% url "repo_revert_file" repo.id %}?commit={{ commit.id }}&p={{commit.path|urlencode}}" class="op vh restore-file">{% trans 'Restore' %}</a>
|
||||
<a href="#" class="op vh restore-file" data-commit_id="{{ commit.id }}" data-commit_path="{{ commit.path }}">{% trans 'Restore' %}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<a href="{% url "download_file" repo.id commit.rev_file_id %}?p={{commit.path|urlencode}}" class="op vh">{% trans 'Download' %}</a>
|
||||
@@ -86,6 +86,26 @@
|
||||
|
||||
{% block extra_script %}
|
||||
<script type="text/javascript">
|
||||
addFormPost($('.restore-file'));
|
||||
$('.restore-file').click(function() {
|
||||
var _this = $(this),
|
||||
commit_id = _this.attr('data-commit_id'),
|
||||
path = _this.attr('data-commit_path');
|
||||
|
||||
$.ajax({
|
||||
url: "{% url 'api-v2.1-file-view' repo.id %}" + "?p=" + encodeURIComponent(path),
|
||||
type: 'POST',
|
||||
data: {'operation': 'revert', 'commit_id': commit_id},
|
||||
cache: false,
|
||||
dataType: 'json',
|
||||
beforeSend: prepareCSRFToken,
|
||||
success: function() {
|
||||
_this.closest('tr').remove();
|
||||
feedback("{% trans "Success" %}", 'success');
|
||||
},
|
||||
error: ajaxErrorHandler
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@@ -64,7 +64,7 @@
|
||||
<td class="alc"><img src="{{ MEDIA_URL }}img/folder-24.png" alt="{% trans "Directory"%}" /></td>
|
||||
<td><a href="{% url 'repo_history_view' repo.id %}?commit_id={{ current_commit.id }}&p={{ path|urlencode }}{{ dirent.obj_name|urlencode }}">{{ dirent.obj_name }}</a></td>
|
||||
<td></td>
|
||||
<td><a class="op vh restore-dir" href="#" data-url="{% url 'repo_revert_dir' repo.id %}?commit={{ current_commit.id }}&p={{ path|urlencode }}{{dirent.obj_name|urlencode}}">{% trans "Restore" %}</a></td>
|
||||
<td><a class="op vh restore-dir" data-commit_id="{{ current_commit.id }}" data-commit_path="{{ path }}{{ dirent.obj_name }}" href="#">{% trans "Restore" %}</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
<td><a class="normal" href="{% url 'view_snapshot_file' repo.props.id %}?obj_id={{ dirent.props.obj_id }}&commit_id={{ current_commit.id }}&p={{ path|urlencode }}{{ dirent.obj_name|urlencode }}" target="_blank">{{ dirent.props.obj_name }}</a></td>
|
||||
<td>{{ dirent.file_size|filesizeformat }}</td>
|
||||
<td>
|
||||
<a class="op vh restore-file" data-url="{% url 'repo_revert_file' repo.id %}?commit={{ current_commit.id }}&p={{ path|urlencode }}{{dirent.obj_name|urlencode}}" href="#">{% trans "Restore" %}</a>
|
||||
<a class="op vh restore-file" data-commit_id="{{ current_commit.id }}" data-commit_path="{{ path }}{{ dirent.obj_name }}" href="#">{% trans "Restore" %}</a>
|
||||
<a class="op vh" href="{% url 'download_file' repo.id dirent.obj_id%}?file_name={{ dirent.obj_name|urlencode }}&p={{path|urlencode}}{{ dirent.obj_name|urlencode }}">{% trans "Download" %}</a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -90,6 +90,33 @@ addConfirmTo($('#repo-revert'), {
|
||||
'post':true
|
||||
});
|
||||
|
||||
addFormPost($('.restore-file, .restore-dir'));
|
||||
$('.restore-file, .restore-dir').click(function() {
|
||||
var _this = $(this),
|
||||
commit_id = _this.attr('data-commit_id'),
|
||||
path = _this.attr('data-commit_path'),
|
||||
url;
|
||||
|
||||
if (_this.hasClass('restore-file')) {
|
||||
url = "{% url 'api-v2.1-file-view' repo.id %}" + "?p=" + encodeURIComponent(path);
|
||||
} else {
|
||||
url = "{% url 'api-v2.1-dir-view' repo.id %}" + "?p=" + encodeURIComponent(path);
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'POST',
|
||||
data: {'operation': 'revert', 'commit_id': commit_id},
|
||||
cache: false,
|
||||
dataType: 'json',
|
||||
beforeSend: prepareCSRFToken,
|
||||
success: function() {
|
||||
_this.closest('tr').remove();
|
||||
feedback("{% trans "Success" %}", 'success');
|
||||
},
|
||||
error: ajaxErrorHandler
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@@ -82,9 +82,7 @@ urlpatterns = patterns(
|
||||
# url(r'^home/public/reply/(?P<msg_id>[\d]+)/$', innerpub_msg_reply, name='innerpub_msg_reply'),
|
||||
# url(r'^home/owner/(?P<owner_name>[^/]+)/$', ownerhome, name='ownerhome'),
|
||||
|
||||
# revert file/dir/repo
|
||||
url(r'^repo/revert_file/(?P<repo_id>[-0-9a-f]{36})/$', repo_revert_file, name='repo_revert_file'),
|
||||
url(r'^repo/revert_dir/(?P<repo_id>[-0-9a-f]{36})/$', repo_revert_dir, name='repo_revert_dir'),
|
||||
# revert repo
|
||||
url(r'^repo/history/revert/(?P<repo_id>[-0-9a-f]{36})/$', repo_revert_history, name='repo_revert_history'),
|
||||
|
||||
(r'^repo/upload_check/$', validate_filename),
|
||||
|
@@ -936,94 +936,6 @@ def render_file_revisions (request, repo_id):
|
||||
'days': days,
|
||||
}, context_instance=RequestContext(request))
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def repo_revert_file(request, repo_id):
|
||||
repo = get_repo(repo_id)
|
||||
if not repo:
|
||||
raise Http404
|
||||
|
||||
commit_id = request.GET.get('commit')
|
||||
path = request.GET.get('p')
|
||||
|
||||
if not (commit_id and path):
|
||||
return render_error(request, _(u"Invalid arguments"))
|
||||
|
||||
referer = request.META.get('HTTP_REFERER', None)
|
||||
next = settings.SITE_ROOT if referer is None else referer
|
||||
|
||||
username = request.user.username
|
||||
# perm check
|
||||
if check_folder_permission(request, repo.id, path) != 'rw':
|
||||
messages.error(request, _("Permission denied"))
|
||||
return HttpResponseRedirect(next)
|
||||
|
||||
is_locked, locked_by_me = check_file_lock(repo_id, path, username)
|
||||
if (is_locked, locked_by_me) == (None, None):
|
||||
messages.error(request, _("Check file lock error"))
|
||||
return HttpResponseRedirect(next)
|
||||
|
||||
if is_locked and not locked_by_me:
|
||||
messages.error(request, _("File is locked"))
|
||||
return HttpResponseRedirect(next)
|
||||
|
||||
try:
|
||||
ret = seafile_api.revert_file(repo_id, commit_id, path, username)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
messages.error(request, _('Failed to restore, please try again later.'))
|
||||
return HttpResponseRedirect(next)
|
||||
|
||||
if ret == 1:
|
||||
root_url = reverse('view_common_lib_dir', args=[repo_id, ''])
|
||||
msg = _(u'Successfully revert %(path)s to <a href="%(root)s">root directory.</a>') % {"path": escape(path.lstrip('/')), "root": root_url}
|
||||
messages.success(request, msg, extra_tags='safe')
|
||||
else:
|
||||
file_view_url = reverse('view_lib_file', args=[repo_id, path.encode('utf-8')])
|
||||
msg = _(u'Successfully revert <a href="%(url)s">%(path)s</a>') % {"url": file_view_url, "path": escape(path.lstrip('/'))}
|
||||
messages.success(request, msg, extra_tags='safe')
|
||||
|
||||
return HttpResponseRedirect(next)
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def repo_revert_dir(request, repo_id):
|
||||
repo = get_repo(repo_id)
|
||||
if not repo:
|
||||
raise Http404
|
||||
|
||||
commit_id = request.GET.get('commit')
|
||||
path = request.GET.get('p')
|
||||
|
||||
if not (commit_id and path):
|
||||
return render_error(request, _(u"Invalid arguments"))
|
||||
|
||||
referer = request.META.get('HTTP_REFERER', None)
|
||||
next = settings.SITE_ROOT if referer is None else referer
|
||||
|
||||
# perm check
|
||||
if check_folder_permission(request, repo.id, path) != 'rw':
|
||||
messages.error(request, _("Permission denied"))
|
||||
return HttpResponseRedirect(next)
|
||||
|
||||
try:
|
||||
ret = seafile_api.revert_dir(repo_id, commit_id, path, request.user.username)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
messages.error(request, _('Failed to restore, please try again later.'))
|
||||
return HttpResponseRedirect(next)
|
||||
|
||||
if ret == 1:
|
||||
root_url = reverse('view_common_lib_dir', args=[repo_id, ''])
|
||||
msg = _(u'Successfully revert %(path)s to <a href="%(url)s">root directory.</a>') % {"path": escape(path.lstrip('/')), "url": root_url}
|
||||
messages.success(request, msg, extra_tags='safe')
|
||||
else:
|
||||
dir_view_url = reverse('view_common_lib_dir', args=[repo_id, path.strip('/').encode('utf-8')])
|
||||
msg = _(u'Successfully revert <a href="%(url)s">%(path)s</a>') % {"url": dir_view_url, "path": escape(path.lstrip('/'))}
|
||||
messages.success(request, msg, extra_tags='safe')
|
||||
|
||||
return HttpResponseRedirect(next)
|
||||
|
||||
@login_required
|
||||
def file_revisions(request, repo_id):
|
||||
"""List file revisions in file version history page.
|
||||
|
@@ -235,6 +235,76 @@ class DirViewTest(BaseTestCase):
|
||||
resp = self.client.post(self.url + '?p=' + self.folder_path, data)
|
||||
self.assertEqual(403, resp.status_code)
|
||||
|
||||
def test_can_revert_folder(self):
|
||||
self.login_as(self.user)
|
||||
|
||||
# first rename dir
|
||||
new_name = randstring(6)
|
||||
seafile_api.rename_file(self.repo_id, '/', self.folder_name,
|
||||
new_name, self.user_name)
|
||||
new_dir_path = '/' + new_name
|
||||
|
||||
# get commit list
|
||||
commits = seafile_api.get_commit_list(self.repo_id, -1, -1)
|
||||
|
||||
# then revert dir
|
||||
data = {
|
||||
'operation': 'revert',
|
||||
'commit_id': commits[0].id
|
||||
}
|
||||
resp = self.client.post(self.url + '?p=' + new_dir_path, data)
|
||||
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
def test_revert_folder_with_invalid_user_permission(self):
|
||||
# first rename dir
|
||||
new_name = randstring(6)
|
||||
seafile_api.rename_file(self.repo_id, '/', self.folder_name,
|
||||
new_name, self.user_name)
|
||||
new_dir_path = '/' + new_name
|
||||
|
||||
# get commit list
|
||||
commits = seafile_api.get_commit_list(self.repo_id, -1, -1)
|
||||
|
||||
# then revert dir
|
||||
data = {
|
||||
'operation': 'revert',
|
||||
'commit_id': commits[0].id
|
||||
}
|
||||
resp = self.client.post(self.url + '?p=' + new_dir_path, data)
|
||||
self.assertEqual(403, resp.status_code)
|
||||
|
||||
def test_revert_folder_with_r_permission(self):
|
||||
# first rename dir
|
||||
new_name = randstring(6)
|
||||
seafile_api.rename_file(self.repo_id, '/', self.folder_name,
|
||||
new_name, self.user_name)
|
||||
new_dir_path = '/' + new_name
|
||||
|
||||
# get commit list
|
||||
commits = seafile_api.get_commit_list(self.repo_id, -1, -1)
|
||||
|
||||
self.share_repo_to_admin_with_r_permission()
|
||||
self.login_as(self.admin)
|
||||
|
||||
# then revert dir
|
||||
data = {
|
||||
'operation': 'revert',
|
||||
'commit_id': commits[0].id
|
||||
}
|
||||
resp = self.client.post(self.url + '?p=' + new_dir_path, data)
|
||||
self.assertEqual(403, resp.status_code)
|
||||
|
||||
def test_revert_folder_without_commit_id(self):
|
||||
self.login_as(self.user)
|
||||
|
||||
# then revert dir
|
||||
data = {
|
||||
'operation': 'revert',
|
||||
}
|
||||
resp = self.client.post(self.url + '?p=' + self.folder_path, data)
|
||||
self.assertEqual(400, resp.status_code)
|
||||
|
||||
# for test http DELETE request
|
||||
def test_can_delete_folder(self):
|
||||
self.login_as(self.user)
|
||||
|
@@ -464,6 +464,76 @@ class FileViewTest(BaseTestCase):
|
||||
resp = self.client.post(url + '?p=/' + admin_file_name, data)
|
||||
self.assertEqual(403, resp.status_code)
|
||||
|
||||
def test_can_revert_file(self):
|
||||
self.login_as(self.user)
|
||||
|
||||
# first rename file
|
||||
new_name = randstring(6)
|
||||
seafile_api.rename_file(self.repo_id, '/', self.file_name,
|
||||
new_name, self.user_name)
|
||||
new_file_path = '/' + new_name
|
||||
|
||||
# get file revisions
|
||||
commits = seafile_api.get_file_revisions(
|
||||
self.repo_id, new_file_path, -1, -1, 100)
|
||||
|
||||
# then revert file
|
||||
data = {
|
||||
'operation': 'revert',
|
||||
'commit_id': commits[0].id
|
||||
}
|
||||
resp = self.client.post(self.url + '?p=' + new_file_path, data)
|
||||
|
||||
self.assertEqual(200, resp.status_code)
|
||||
|
||||
def test_revert_file_with_invalid_user_permission(self):
|
||||
# first rename file
|
||||
new_name = randstring(6)
|
||||
seafile_api.rename_file(self.repo_id, '/', self.file_name,
|
||||
new_name, self.user_name)
|
||||
new_file_path = '/' + new_name
|
||||
|
||||
# get file revisions
|
||||
commits = seafile_api.get_file_revisions(
|
||||
self.repo_id, new_file_path, -1, -1, 100)
|
||||
|
||||
# then revert file
|
||||
data = {
|
||||
'operation': 'revert',
|
||||
'commit_id': commits[0].id
|
||||
}
|
||||
resp = self.client.post(self.url + '?p=' + new_file_path, data)
|
||||
self.assertEqual(403, resp.status_code)
|
||||
|
||||
def test_revert_file_with_r_permission(self):
|
||||
# first rename file
|
||||
new_name = randstring(6)
|
||||
seafile_api.rename_file(self.repo_id, '/', self.file_name,
|
||||
new_name, self.user_name)
|
||||
new_file_path = '/' + new_name
|
||||
|
||||
# get file revisions
|
||||
commits = seafile_api.get_file_revisions(
|
||||
self.repo_id, new_file_path, -1, -1, 100)
|
||||
|
||||
self.share_repo_to_admin_with_r_permission()
|
||||
self.login_as(self.admin)
|
||||
# then revert file
|
||||
data = {
|
||||
'operation': 'revert',
|
||||
'commit_id': commits[0].id
|
||||
}
|
||||
resp = self.client.post(self.url + '?p=' + new_file_path, data)
|
||||
self.assertEqual(403, resp.status_code)
|
||||
|
||||
def test_revert_file_without_commit_id(self):
|
||||
self.login_as(self.user)
|
||||
data = {
|
||||
'operation': 'revert',
|
||||
}
|
||||
resp = self.client.post(self.url + '?p=' + self.file_path, data)
|
||||
self.assertEqual(400, resp.status_code)
|
||||
|
||||
# for test http PUT request
|
||||
def test_can_lock_file(self):
|
||||
|
||||
|
Reference in New Issue
Block a user