1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-08-01 15:23:05 +00:00

Merge branch '5.0'

Conflicts:
	seahub/api2/urls.py
	seahub/api2/views.py
	seahub/group/templates/group/group_discuss.html
	seahub/options/models.py
	seahub/templates/repo_folder_perm.html
	seahub/templates/repo_share_manage.html
	seahub/templates/snippets/shared_link_js.html
	seahub/test_utils.py
This commit is contained in:
zhengxie 2016-03-28 12:16:30 +08:00
commit ec9be6975d
29 changed files with 411 additions and 606 deletions

View File

@ -300,6 +300,9 @@ td, th { padding:5px 3px; word-wrap:break-word; border-bottom:1px solid #eee; }
td { color: #333; font-size:14px; }
table img { vertical-align:middle; }
p { margin:0.5em 0; }
:focus {/* to overwrite user agent stylesheet */
outline:none;
}
/********** common class ***********/
.hl { background-color: #f8f8f8; }/*highlight*/

View File

@ -218,17 +218,6 @@ $('#logout').click(function() {
}
}
});
if ($.browser.mozilla || $.browser.msie) {
$('a').focus(function() {
$(this).blur();
});
}
if ($.browser.msie) {
$('button, input[type="checkbox"], input[type="radio"], input[type="submit"]').focus(function() {
$(this).blur();
});
$('.search-input').css({'line-height':$('.search-input').css('height')});
}
/*
* add confirm to an operation, using a popup

169
media/js/jq.min.js vendored

File diff suppressed because one or more lines are too long

5
media/js/jquery-1.12.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -76,7 +76,6 @@ class Account(APIView):
profile.intro = note
profile.save()
refresh_profile_cache(email)
def _update_account_quota(self, request, email):
storage = request.data.get("storage", None)

View File

@ -52,13 +52,14 @@ urlpatterns = patterns('',
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/revision/$', FileRevision.as_view()),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/file/revert/$', FileRevert.as_view()),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/file/revert/$', FileRevert.as_view(), name='api2-file-revert'),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/file/shared-link/$', FileSharedLinkView.as_view()),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/dir/$', DirView.as_view(), name='DirView'),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/dir/sub_repo/$', DirSubRepoView.as_view()),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/dir/share/$', DirShareView.as_view()),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/dir/shared_items/$', DirSharedItemsEndpoint.as_view(), name="api2-dir-shared-items"),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/dir/download/$', DirDownloadView.as_view(), name='api2-dir-download'),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/dir/revert/$', DirRevert.as_view(), name='api2-dir-revert'),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/thumbnail/$', ThumbnailView.as_view(), name='api2-thumbnail'),
url(r'^starredfiles/', StarredFileView.as_view(), name='starredfiles'),
url(r'^devices/', DevicesView.as_view(), name='api2-devices'),

View File

@ -1861,6 +1861,7 @@ class OwaFileView(APIView):
send_file_access_msg(request, repo, path, 'api')
return Response(wopi_dict)
class DevicesView(APIView):
"""List user devices"""
authentication_classes = (TokenAuthentication, SessionAuthentication)
@ -1894,6 +1895,7 @@ class DevicesView(APIView):
return Response({'success': True})
class FileView(APIView):
"""
Support uniform interface for file related operations,
@ -2268,38 +2270,44 @@ class FileDetailView(APIView):
content_type=json_content_type)
class FileRevert(APIView):
authentication_classes = (TokenAuthentication, )
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle, )
def put(self, request, repo_id, format=None):
path = request.data.get('p', '')
path = request.data.get('p', None)
commit_id = request.data.get('commit_id', None)
if not path:
return api_error(status.HTTP_400_BAD_REQUEST, 'Path is missing.')
error_msg = 'path invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if not commit_id:
error_msg = 'commit_id invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if not seafile_api.get_repo(repo_id):
error_msg = 'library %s not found.' % repo_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
if not seafile_api.get_file_id_by_commit_and_path(repo_id, commit_id, path):
error_msg = 'file %s not found.' % path
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
if check_folder_permission(request, repo_id, '/') != 'rw':
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
username = request.user.username
is_locked, locked_by_me = check_file_lock(repo_id, path, username)
if (is_locked, locked_by_me) == (None, None):
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Check file lock error')
if is_locked and not locked_by_me:
return api_error(status.HTTP_403_FORBIDDEN, 'File is locked')
parent_dir = os.path.dirname(path)
if check_folder_permission(request, repo_id, parent_dir) != 'rw':
return api_error(status.HTTP_403_FORBIDDEN,
'You do not have permission to access this folder.')
path = unquote(path.encode('utf-8'))
commit_id = unquote(request.data.get('commit_id', '').encode('utf-8'))
try:
ret = seafserv_threaded_rpc.revert_file(repo_id, commit_id,
path, username)
seafile_api.revert_file(repo_id, commit_id, path, username)
except SearpcError as e:
logger.error(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, "Internal error")
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
return Response({'success': True})
return HttpResponse(json.dumps({"ret": ret}), status=200, content_type=json_content_type)
class FileRevision(APIView):
authentication_classes = (TokenAuthentication, )
@ -2698,6 +2706,45 @@ class DirDownloadView(APIView):
return HttpResponse(json.dumps(redirect_url), status=200,
content_type=json_content_type)
class DirRevert(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle, )
def put(self, request, repo_id):
path = request.data.get('p', None)
commit_id = request.data.get('commit_id', None)
if not path:
error_msg = 'path invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if not commit_id:
error_msg = 'commit_id invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if not seafile_api.get_repo(repo_id):
error_msg = 'library %s not found.' % repo_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
if not seafile_api.get_dir_id_by_commit_and_path(repo_id, commit_id, path):
error_msg = 'folder %s not found.' % path
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
if check_folder_permission(request, repo_id, '/') != 'rw':
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
username = request.user.username
try:
seafile_api.revert_dir(repo_id, commit_id, path, username)
except SearpcError 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})
class DirShareView(APIView):
authentication_classes = (TokenAuthentication, )
permission_classes = (IsAuthenticated,)

View File

@ -71,7 +71,7 @@ def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False):
if autoescape and not safe_input:
lead, trail = escape(lead), escape(trail)
url, trimmed = escape(url), escape(trimmed)
middle = '<a target="_blank" href="%s"%s>%s</a>' % (url, nofollow_attr, trimmed)
middle = '<a href="%s"%s>%s</a>' % (url, nofollow_attr, trimmed)
words[i] = mark_safe('%s%s%s' % (lead, middle, trail))
else:
if safe_input:

View File

@ -187,12 +187,18 @@ class UserOptionsManager(models.Manager):
- `self`:
- `username`:
"""
try:
user_option = super(UserOptionsManager, self).get(
email=username, option_key=KEY_DEFAULT_REPO)
return user_option.option_val
except UserOptions.DoesNotExist:
user_options = super(UserOptionsManager, self).filter(
email=username, option_key=KEY_DEFAULT_REPO)
if len(user_options) == 0:
return None
elif len(user_options) == 1:
return user_options[0].option_val
else:
for o in user_options[1: len(user_options)]:
o.delete()
return user_options[0].option_val
def passwd_change_required(self, username):
"""Check whether user need to change password.
@ -211,10 +217,10 @@ class UserOptionsManager(models.Manager):
def unset_force_passwd_change(self, username):
return self.unset_user_option(username, KEY_FORCE_PASSWD_CHANGE)
class UserOptions(models.Model):
email = LowerCaseCharField(max_length=255, db_index=True)
option_key = models.CharField(max_length=50)
option_val = models.CharField(max_length=50)
objects = UserOptionsManager()

View File

@ -129,11 +129,19 @@ class DetailedProfile(models.Model):
telephone = models.CharField(max_length=100)
objects = DetailedProfileManager()
########## signal handler
########## signal handlers
from django.db.models.signals import post_save
from .utils import refresh_cache
@receiver(user_registered)
def clean_email_id_cache(sender, **kwargs):
from seahub.utils import normalize_cache_key
user = kwargs['user']
key = normalize_cache_key(user.email, EMAIL_ID_CACHE_PREFIX)
cache.set(key, user.id, EMAIL_ID_CACHE_TIMEOUT)
@receiver(post_save, sender=Profile, dispatch_uid="update_nickname_cache")
def update_nickname_cache(sender, instance, **kwargs):
refresh_cache(instance.user)

View File

@ -36,9 +36,7 @@ def edit_profile(request):
if form.is_valid():
form.save(username=username)
messages.success(request, _(u'Successfully edited profile.'))
# refresh nickname cache
refresh_cache(request.user.username)
return HttpResponseRedirect(reverse('edit_profile'))
else:
messages.error(request, _(u'Failed to edit profile'))

View File

@ -1,4 +1,4 @@
{% load seahub_tags avatar_tags group_avatar_tags i18n %}
{% load seahub_tags avatar_tags group_avatar_tags i18n staticfiles %}
{% load url from future %}
<!DOCTYPE html>
@ -21,7 +21,7 @@
{% block info_bar_message %}
{% if request.user.is_authenticated and request.cur_note %}
<div id="info-bar">
<p id="info-bar-info">{{ request.cur_note.message|urlize|url_target_blank }}</p>
<p id="info-bar-info">{{ request.cur_note.message|urlize }}</p>
<span class="close sf2-icon-x1 op-icon" data="{{ request.cur_note.id }}" title="{% trans "Close" %}"></span>
</div>
{% endif %}
@ -149,7 +149,10 @@
{% include 'footer.html' %}
</div><!-- wrapper -->
<script type="text/javascript" src="{{ MEDIA_URL }}js/jq.min.js?t=1398068110"></script>
<script type="text/javascript" src="{{ MEDIA_URL }}js/jquery-1.12.1.min.js"></script>
<script type="text/javascript" src="{% static "scripts/lib/jquery.simplemodal.js" %}"></script>
<script type="text/javascript" src="{% static "scripts/lib/jquery.ui.tabs.js" %}"></script>
<script type="text/javascript" src="{{ MEDIA_URL }}js/jq.min.js"></script>
<script type="text/javascript" src="{{ MEDIA_URL }}js/base.js?t=1404370380"></script>
<script type="text/javascript">
$.jstree._themes = '{{ MEDIA_URL }}js/themes/';

View File

@ -31,7 +31,7 @@
{% block info_bar_message %}
{% if request.user.is_authenticated and request.cur_note %}
<div id="info-bar">
<p id="info-bar-info">{{ request.cur_note.message|urlize|url_target_blank }}</p>
<p id="info-bar-info">{{ request.cur_note.message|urlize }}</p>
<span class="close sf2-icon-x1 op-icon" title="{% trans "Close" %}"></span>
</div>
{% endif %}

View File

@ -125,12 +125,31 @@ var get_more_trash = function(current_scan_stat) {
});
}
$('table').on("click", ".restore-file, .restore-dir", function() {
$('<form>', {
"method": 'POST',
"action": $(this).data('url'),
"html": '<input name="csrfmiddlewaretoken" value="' + getCookie('csrftoken') + '" type="hidden">'
}).appendTo(document.body).submit();
var _this = $(this),
commit_id = _this.data('commit_id'),
path = _this.data('path');
$.ajax({
url: _this.data('url'),
type: 'PUT',
dataType: 'json',
cache: false,
beforeSend: prepareCSRFToken,
data: {'commit_id': commit_id, 'p': path},
success: function(data) {
_this.closest('tr').remove();
feedback("{% trans "Success" %}", 'success');
},
error: function ajaxErrorHandler(xhr, textStatus, errorThrown) {
if (xhr.responseText) {
feedback($.parseJSON(xhr.responseText).error_msg, 'error');
} else {
feedback("{% trans "Failed. Please check the network." %}", 'error');
}
}
});
return false;
});

View File

@ -38,7 +38,7 @@ $('#add-file-popup .submit').click(function() {
$.modal.close();
var files = '';
for (var i = 0, len = selected.length; i < len; i++) {
files += '<li class="item">' + '<img src="{{MEDIA_URL}}img/del.png" class="rm vam" data-index="' + i + '" /><span class="vam">' + selected[i].substr(selected[i].lastIndexOf('/') + 1) + '</span></li>';
files += '<li class="item">' + '<img src="{{MEDIA_URL}}img/del.png" class="rm vam" data-index="' + i + '" /><span class="vam">' + HTMLescape(selected[i].substr(selected[i].lastIndexOf('/') + 1)) + '</span></li>';
}
files_ct.data('files', selected).html(files).removeClass('hide');
$('.rm', files_ct).click(function() {

View File

@ -1,15 +1,9 @@
{% load i18n %} {# for file/dir share #}
{% load i18n %}
<div id="file-share" class="hide">
<h3 class="hd">{% trans 'Share %(name)s' %}</h3>
<div id="file-share-tabs" class="left-right-tabs ovhd">
<ul class="left-right-tabs-nav fleft">
<li class="tab"><a href="#link-share" class="a">{% trans "Download Link" %}</a></li>
{% if user_perm == 'rw' %}
<li class="tab" id="upload-link-share-tab"><a href="#upload-link-share" class="a">{% trans "Upload Link" %}</a></li>{# for dir #}
{% endif %}
{% if ENABLE_SUB_LIBRARY and not repo.is_virtual and is_repo_owner %}
<li class="tab" id="syncable-share-tab"><a href="#syncable-share" class="a">{% trans "Private Share" %}</a></li> {# for dir #}
{% endif %}
</ul>
<div class="fright">
@ -58,51 +52,6 @@
</div>
</div>
{% if user_perm == 'rw' %}
<div id="upload-link-share" class="tabs-panel">
<p class="tip">{% trans "You can share the generated link to others and then they can upload files to this directory via the link." %}</p>
<div id="upload-link-options">
<label class="checkbox-label">
<span class="checkbox"><input type="checkbox" name="use-passwd" id="upload-link-passwd-switch" class="checkbox-orig" /></span>
<span class="checkbox-option">{% trans "Add password protection"%}</span>
</label>
<div id="upload-link-passwd" class="hide">
<label>{% trans "Password"%}</label><span class="tip">{% blocktrans %}(at least {{share_link_password_min_length}} characters){% endblocktrans %}</span><br />
<input type="password" name="passwd" disabled="disabled" class="input input-disabled" /><br />
<label>{% trans "Password again"%}</label><br />
<input type="password" name="passwd_again" disabled="disabled" class="input input-disabled" />
</div>
<p class="error hide"></p>
</div>
<button id="gen-upload-link-btn" class="hide">{% trans "Generate"%}</button>
<div id="share-upload-link-body" class="hide">
<p><span class="vam">{% trans 'Upload Link: ' %}</span><input type="text" readonly="readonly" id="shared-upload-link-text" class="vam" /></p>
<button id="send-upload-link">{% trans 'Send' %}</button>
<button id="rm-shared-upload-link">{% trans 'Delete' %}</button>
<form id="upload-link-send-form" action="" method="post" class="hide">{% csrf_token %}
<label>{% trans "Send to:"%}</label><br />
<input type="text" class="input" id="upload-link-send-input" name="email" placeholder="{% trans "Emails, Seperated by ','"%}" /><br />
<input type="hidden" name="shared_upload_link" value="{{ dir_shared_upload_link }}" />
<label>{% trans "Message (optional):"%}</label><br />
<textarea class="textarea" name="extra_msg" id="upload-extra-msg-text"></textarea><br />
<p class="error hide"></p>
<input type="submit" value="{% trans "Submit"%}" class="submit" />
<input type="button" value="{% trans "Cancel"%}" class="cancel" />
<p id="upload-sending" class="hide">{% trans "Sending..."%}</p>
</form>
</div>
</div>
{% endif %}
{% if ENABLE_SUB_LIBRARY and not repo.is_virtual and is_repo_owner %}
<div id="syncable-share" class="tabs-panel">
{% url 'share_repo' as repo_share_url %}
{% with post_url=repo_share_url %}
{% include "snippets/repo_share_form.html" %} {# for sub-lib share #}
{% endwith %}
</div>
{% endif %}
</div>
</div>

View File

@ -8,7 +8,7 @@
<td><a href="?commit_id={{ dirent.commit_id }}&base={{ dirent.basedir|urlencode }}&p=/{{ dirent.obj_name|urlencode }}&dir_path={{dir_path|urlencode}}">{{ dirent.obj_name }}</a></td>
<td>{{ dirent.delete_time|translate_seahub_time }}</td>
<td></td>
<td><a class="op restore-dir hide" href="#" data-url="{% url 'repo_revert_dir' repo.id %}?commit={{ dirent.commit_id }}&p={{ dirent.basedir|urlencode }}{{dirent.obj_name|urlencode}}">{% trans "Restore" %}</a></td>
<td><a class="op restore-dir hide" href="#" data-commit_id="{{dirent.commit_id}}" data-path="{{dirent.basedir}}{{dirent.obj_name}}" data-url="{% url 'api2-dir-revert' repo.id %}">{% trans "Restore" %}</a></td>
{% else %}
<td><a href="?commit_id={{ commit_id }}&base={{ basedir|urlencode }}&p={{ path|urlencode }}{{ dirent.obj_name|urlencode }}&dir_path={{dir_path|urlencode}}">{{ dirent.obj_name }}</a></td>
<td></td>
@ -23,7 +23,7 @@
<td><a class="normal" href="{% url 'view_trash_file' repo.id %}?obj_id={{ dirent.obj_id }}&commit_id={{ dirent.commit_id }}&base={{ dirent.basedir|urlencode }}&p=/{{ dirent.obj_name|urlencode }}" target="_blank">{{ dirent.obj_name }}</a></td>
<td>{{ dirent.delete_time|translate_seahub_time }}</td>
<td>{{ dirent.file_size|filesizeformat }}</td>
<td><a class="op restore-file hide" href="#" data-url="{% url 'repo_revert_file' repo.id %}?commit={{ dirent.commit_id }}&p={{ dirent.basedir|urlencode }}{{dirent.obj_name|urlencode}}">{% trans "Restore" %}</a></td>
<td><a class="op restore-file hide" href="#" data-commit_id="{{dirent.commit_id}}" data-path="{{dirent.basedir}}{{dirent.obj_name}}" data-url="{% url 'api2-file-revert' repo.id %}">{% trans "Restore" %}</a></td>
{% else %}
<td><a class="normal" href="{% url 'view_trash_file' repo.id %}?obj_id={{ dirent.obj_id }}&commit_id={{ commit_id }}&base={{ basedir|urlencode }}&p={{ path|urlencode }}{{ dirent.obj_name|urlencode }}" target="_blank">{{ dirent.obj_name }}</a></td>
<td></td>
@ -33,4 +33,3 @@
</tr>
{% endif %}
{% endfor %}

View File

@ -1,7 +1,8 @@
{% load i18n %}
{% load url from future %}
var share_list = [], contacts = [];
/*
var share_list = [];
$(function () {
$.ajax({
url:'{% url 'get_contacts' %}',
@ -12,69 +13,21 @@ $(function () {
for (var i = 0, len = contact_list.length; i < len; i++) {
contact_email = contact_list[i].email;
share_list.push({value: contact_email, label: contact_email});
contacts.push({value:contact_email, avatar:contact_list[i].avatar});
}
}
});
});
*/
function showSharePopup(op, name, aj_data, type, cur_path) {
var path;
if (op.attr('id') == 'share-cur-dir') {
path = cur_path.substr(0, cur_path.length - 1); // rm the last '/' as seafile_api treats '/xx' and '/xx/' as different
} else {
path = cur_path + name;
}
var path = cur_path + name;
var form = $('#file-share');
{% if ENABLE_SUB_LIBRARY and not repo.is_virtual and is_repo_owner %}
var grp_options_ct = $('#share-grp-options');
if (!$.trim(grp_options_ct.html())) {
var grp_options = '<ul class="option-list">';
{% for group in request.user.joined_groups %}
grp_options += '<li> <label class="checkbox-label"> <span class="checkbox"><input type="checkbox" name="grp" value="{{ group.group_name }}" class="checkbox-orig"/></span> {{group.avatar|safe}} <span class="checkbox-option">' + "{{ group.group_name }}" + '</span> </label> </li>';
{% endfor %}
grp_options += '</ul>';
grp_options_ct.html(grp_options);
}
var contact_options_ct = $('#share-contact-options');
if (!$.trim(contact_options_ct.html())) {
var contact_options = '<ul class="option-list">';
for (var i = 0, len = contacts.length; i < len; i++) {
contact_email = contacts[i].value;
contact_options += ' <li> <label class="checkbox-label"> <span class="checkbox"><input type="checkbox" name="contact" value="' + contact_email + '" class="checkbox-orig" /></span>' + contacts[i].avatar + ' <span class="checkbox-option">' + contact_email + '</span> </label> </li>';
}
contact_options += '</ul>';
contact_options_ct.html(contact_options);
}
$('.checkbox-orig', $('#share-grp-options, #share-contact-options')).click(function() {
$(this).parent().toggleClass('checkbox-checked');
});
$(".checkbox-label", $('#share-grp-options, #share-contact-options')).hover(
function() {
$(this).addClass('hl');
},
function() {
$(this).removeClass('hl');
}
);
{% endif %}
form.modal({
appendTo: "#main",
focus: false
});
form.modal({appendTo: "#main",'focus':false, containerCss:{"padding":0}});
var hd = $('#file-share .hd');
hd.html(hd.html().replace('%(name)s', '<span class="op-target">' + HTMLescape(trimFilename(name, 30)) + '</span>'));
if (type == 'd') {
$('#private-share-tab, #private-share').remove();
} else {
$('#syncable-share-tab, #syncable-share, #upload-link-share-tab, #upload-link-share').remove();
}
$("#file-share-tabs").tabs();
// share link
@ -95,61 +48,6 @@ function showSharePopup(op, name, aj_data, type, cur_path) {
$('input[name="file_shared_name"]').val(name);
$('input[name="file_shared_type"]').val(type);
{% if user_perm == 'rw' %}
// share upload link
$('#upload-link-share-tab .a').click(function() {
if (op.attr('data-upload-link')) {
$('#gen-upload-link-btn, #upload-link-options').addClass('hide');
$('#share-upload-link-body').removeClass('hide');
var link = op.attr('data-upload-link');
$('#shared-upload-link-text, #upload-link-send-form input[name="shared_upload_link"]').val(link);
$('#main').append('<p id="linkwidth" class="hide">' + link + '</p>');
$('#shared-upload-link-text').css({'width':$('#linkwidth').width() + 25});
$('#linkwidth').remove();
} else {
$('#gen-upload-link-btn, #upload-link-options').removeClass('hide');
$('#share-upload-link-body').addClass('hide');
}
$('#gen-upload-link-btn').data('aj_data', aj_data).data('obj', op);
$('#rm-shared-upload-link').data('obj', op);
});
{% endif %}
// 'private share' for file
$('#private-share-tab a').click(function() {
var form = $('#private-share-form');
$("input[name=s_type]", form).val(type);
$("input[name=path]", form).val(path);
var opts = '', email, avatar;
for (var i = 0, len = contacts.length; i < len; i++) {
email = contacts[i].value;
opts += '<option value="' + email + '" data-index="' + i + '">' + email + '</option>';
}
var format = function(item) {
return contacts[$(item.element).data('index')].avatar + '<span class="vam">' + item.text + '</span>';
}
$('[name="emails"]', form).html(opts).select2({
placeholder: "{% trans "Select contacts" %}",
formatResult: format,
formatSelection: format,
escapeMarkup: function(m) { return m; }
});
});
{% if ENABLE_SUB_LIBRARY and not repo.is_virtual and is_repo_owner %}
// syncable share for dir
$('#syncable-share-tab a').click(function() {
var form = $("#repo-share-form");
form.removeClass('hide').css({'width':'auto', 'padding-top':0});
$("h3", form).remove();
$('.checkbox-label', form).css({'margin-right':'3px'}); // make it not show on top of the scrollbar when hover
form.data('dir-path', path);
$("#repo-share-tabs").tabs();
$('#repo-share-tabs .ui-tabs-nav').css({'padding-left': '1.4em'});
});
{% endif %}
$('#simplemodal-container').css({'height':'auto', 'width':'auto'});
}
@ -161,7 +59,7 @@ $('#send-link').click(function() {
var text = $('#download-extra-msg-text');
text.css({'width': $('#link-share').width() - parseInt(text.css('padding-left')) - parseInt(text.css('padding-right')) - parseInt(text.css('border-left-width')) - parseInt(text.css('border-right-width'))});
$('#link-send-form').removeClass('hide');
addAutocomplete('#link-send-input', '#link-send-form', share_list);
//addAutocomplete('#link-send-input', '#link-send-form', share_list);
});
$("#link-send-form .cancel").click(function() {
@ -241,8 +139,8 @@ $('#gen-link-btn').click(function() {
obj = gen_link_btn.data('obj'),
form = $('#link-options'),
form_id = form.attr('id'),
use_passwd = $('#link-passwd-switch').attr('checked'),
set_expiration = $('#link-expire-switch').attr('checked'),
use_passwd = $('#link-passwd-switch').prop('checked'),
set_expiration = $('#link-expire-switch').prop('checked'),
passwd, passwd_again, expire_days, post_data;
if (use_passwd) {
@ -332,193 +230,12 @@ $('#rm-shared-link').click(function() {
});
});
{% if user_perm == 'rw' %}
$('#shared-upload-link-text').click(function() {
$(this).select();
});
$('#send-upload-link').click(function() {
$(this).addClass('hide');
$('#rm-shared-upload-link').addClass('hide');
var input = $('#upload-link-send-input');
input.css({'width': $('#upload-link-share').width() - parseInt(input.css('padding-left')) - parseInt(input.css('padding-right')) - parseInt(input.css('border-left-width')) - parseInt(input.css('border-right-width'))});
var text = $('#upload-extra-msg-text');
text.css({'width': $('#upload-link-share').width() - parseInt(text.css('padding-left')) - parseInt(text.css('padding-right')) - parseInt(text.css('border-left-width')) - parseInt(text.css('border-right-width'))});
$('#upload-link-send-form').removeClass('hide');
addAutocomplete('#upload-link-send-input', '#upload-link-send-form', share_list);
});
$("#upload-link-send-form .cancel").click(function() {
$('#upload-link-send-form, #send-upload-link, #rm-shared-upload-link').toggleClass('hide');
});
$("#upload-link-send-form").submit(function(event) {
var form = $(this),
shared_upload_link = form.children('input[name="shared_upload_link"]').val(),
email = $.trim(form.children('input[name="email"]').val()),
submit_btn = form.children('input[type="submit"]'),
extra_msg = form.children('textarea[name="extra_msg"]').val();
if (!email) {
apply_form_error('upload-link-send-form', "{% trans "Please input at least an email." %}");
return false;
}
disable(submit_btn);
$('#upload-link-send-form .error').addClass('hide');
$('#upload-sending').removeClass('hide');
$.ajax({
type: "POST",
url: "{% url 'send_shared_upload_link' %}",
dataType: 'json',
cache: false,
beforeSend: prepareCSRFToken,
data: {
shared_upload_link: shared_upload_link,
email: email,
extra_msg: extra_msg
},
success: function(data) {
$.modal.close();
var msg = "{% trans "Successfully sent to {placeholder}" %}"
.replace('{placeholder}', data['send_success'].join(', '));
feedback(msg, "success");
if (data['send_failed'].length > 0) {
msg += '<br />' + "{% trans "Failed to send to {placeholder}" %}"
.replace('{placeholder}', data['send_failed'].join(', '));
feedback(msg, 'info');
}
},
error: function(xhr, textStatus, errorThrown) {
$('#upload-sending').addClass('hide');
enable(submit_btn);
var err_str = '';
if (xhr.responseText) {
var err = jQuery.parseJSON(xhr.responseText);
if (err.error) {
err_str = err.error;
} else {
for (var i in err) {
err_str += err[i];
}
}
} else {
err_str = "{% trans "Failed. Please check the network." %}";
}
apply_form_error('upload-link-send-form', err_str);
}
});
return false;
});
$('#gen-upload-link-btn').click(function() {
var gen_upload_link_btn = $(this),
obj = gen_upload_link_btn.data('obj'),
form = $('#upload-link-options'),
form_id = form.attr('id'),
passwd_switch = $('#upload-link-passwd-switch'),
use_passwd = passwd_switch.attr('checked'),
passwd, passwd_again, post_data;
if (use_passwd) {
passwd = $('input[name="passwd"]', form).val();
passwd_again = $('input[name="passwd_again"]', form).val();
if (!$.trim(passwd)) {
apply_form_error(form_id, "{% trans "Please enter password" %}");
return false;
}
if ($.trim(passwd).length < {{share_link_password_min_length}}) {
apply_form_error(form_id, "{% trans "Password is too short" %}");
return false;
}
if (!$.trim(passwd_again)) {
apply_form_error(form_id, "{% trans "Please enter the password again" %}");
return false;
}
if ($.trim(passwd) != $.trim(passwd_again)) {
apply_form_error(form_id, "{% trans "Passwords don't match" %}");
return false;
}
post_data = {'use_passwd': 1, 'passwd': passwd};
} else {
post_data = {'use_passwd': 0};
}
$.ajax({
url: '{% url 'ajax_get_upload_link' %}',
type: 'POST',
dataType: 'json',
beforeSend: prepareCSRFToken,
data: $.extend(post_data, {
'repo_id': $(this).data('aj_data').repo_id,
'p': $(this).data('aj_data').p
}),
success: function(data) {
var upload_link = data['upload_link'];
gen_upload_link_btn.addClass('hide');
$('#upload-link-options, #upload-link-options .error').addClass('hide');
$('#upload-link-passwd').hide();
$('#upload-link-passwd-switch').attr('checked', false).parent().removeClass('checkbox-checked');
$('[type="password"]', form).val('').attr('disabled', false).removeClass('input-disabled');
$('#shared-upload-link-text, #upload-link-send-form input[name="shared_upload_link"]').val(upload_link);
$('#main').append('<p id="linkwidth" class="hide">' + upload_link + '</p>');
$('#shared-upload-link-text').css({'width':$('#linkwidth').width() + 25});
$('#linkwidth').remove();
$('#share-upload-link-body').removeClass('hide');
obj.attr({'data-upload-link': upload_link, 'data-upload-token': data['token']});
},
error: function(xhr, textStatus, errorThrown) {
location.reload(true);
}
});
return false;
});
$('#rm-shared-upload-link').click(function() {
var obj = $(this).data('obj'),
token = obj.attr('data-upload-token');
$.ajax({
url: '{% url 'ajax_remove_shared_upload_link' %}',
type: 'POST',
data: {'t': token},
dataType: 'json',
cache: false,
beforeSend: prepareCSRFToken,
success: function(data) {
$('#share-upload-link-body').addClass('hide');
$('#upload-link-options, #gen-upload-link-btn').removeClass('hide');
obj.attr({'data-upload-link': '', 'data-upload-token':''});
},
error:ajaxErrorHandler
});
});
$('#upload-link-passwd-switch').click(function () {
var form = $('#upload-link-options'),
pwd_input = $('input[type="password"]', form);
var link_passwd = $('#upload-link-passwd');
if ($(this).attr('checked')) {
pwd_input.attr('disabled', false).removeClass('input-disabled');
link_passwd.slideDown(100);
} else {
link_passwd.slideUp(100);
pwd_input.attr('disabled', true).addClass('input-disabled');
}
});
{% endif %}
$('#link-passwd-switch').click(function () {
var form = $('#link-options'),
pwd_input = $('input[type="password"]', form);
var link_passwd = $('#link-passwd');
if ($(this).attr('checked')) {
if ($(this).prop('checked')) {
pwd_input.attr('disabled', false).removeClass('input-disabled');
link_passwd.slideDown(100);
} else {
@ -532,7 +249,7 @@ $('#link-expire-switch').click(function () {
days_input = $('input[name="expire-days"]', form);
var link_expire = $('#link-expire');
if ($(this).attr('checked')) {
if ($(this).prop('checked')) {
link_expire.slideDown(100);
days_input.attr('disabled', false).removeClass('input-disabled');
} else {

View File

@ -49,51 +49,43 @@
{% block extra_script %}
<script type="text/javascript">
var user_list = [], user_email;
{% for user in not_admin_users %}
user_email = '{{ user.email }}';
user_list.push({value:user_email, label:user_email});
{% endfor %}
$('#add-admin-btn').click(function() {
var form = $("#add-admin-form");
form.modal({appendTo: "#main", focus:false});
$('#simplemodal-container').css({'height':'auto', 'padding':0});
$('#add-admin-tabs').tabs();
addAutocomplete('#added-member-name', '#enter', user_list);
});
$('#add-admin-form').submit(function() {
var form = $(this),
cur_tab_id = $('.ui-tabs-selected a', form).attr('href'),
post_data = '',
input = $('[name="user_email"]', form);
post_data = input.val();
var $form = $(this),
emails = $.trim($('[name="user_email"]', $form).val());
if (!post_data) {
apply_form_error(form.attr('id'), '{% trans "Please enter emails, or select some." %}');
if (!emails) {
return false;
}
}
var submit_btn = $('[type="submit"]', form);
disable(submit_btn);
var $submitBtn = $('[type="submit"]', $form);
disable($submitBtn);
$.ajax({
url: '{% url 'batch_user_make_admin' %}',
url: '{% url 'batch_user_make_admin' %}',
type: 'POST',
dataType: 'json',
cache: false,
beforeSend: prepareCSRFToken,
data: {
'set_admin_emails': post_data
'set_admin_emails': emails
},
success: function(data) {
location.reload('true');
success: function() {
location.reload(true);
},
error: function(data, textStatus, jqXHR) {
var errors = $.parseJSON(data.responseText);
$.each(errors, function(index, value) {
apply_form_error(form.attr('id'), value);
});
enable(submit_btn);
error: function(xhr) {
var error_msg;
if (xhr.responseText) {
error_msg = $.parseJSON(xhr.responseText).error;
} else {
error_msg = "{% trans "Failed. Please check the network." %}";
}
$('.error', $form).html(error_msg).removeClass('hide');
enable($submitBtn);
}
});

View File

@ -1,11 +1,11 @@
from .settings import *
# no cache for testing
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
}
}
# CACHES = {
# 'default': {
# 'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
# }
# }
# enlarge api throttle
REST_FRAMEWORK = {
@ -15,3 +15,7 @@ REST_FRAMEWORK = {
'user': '300/minute',
},
}
# Use static file storage instead of cached, since the cached need to run collect
# command first.
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'

View File

@ -1,6 +1,7 @@
import os
from uuid import uuid4
from django.core.cache import cache
from django.core.urlresolvers import reverse
from django.conf import settings
from django.http import SimpleCookie
@ -161,3 +162,8 @@ class BaseTestCase(TestCase, Fixtures):
if session_cookie:
session.delete(session_key=session_cookie.value)
self.client.cookies = SimpleCookie()
def clear_cache(self):
# clear cache between every test case to avoid config option cache
# issue which cause test failed
cache.clear()

View File

@ -0,0 +1,63 @@
import os
import json
from seaserv import seafile_api
from django.core.urlresolvers import reverse
from seahub.test_utils import BaseTestCase
class DirRevertTest(BaseTestCase):
def setUp(self):
self.repo_id = self.repo.id
self.folder_path = self.folder
self.parent_dir = os.path.dirname(self.folder_path)
self.folder_name = os.path.basename(self.folder_path)
self.username = self.user.username
self.url = reverse('api2-dir-revert', args=[self.repo_id])
def tearDown(self):
self.remove_repo()
def delete_dir(self):
seafile_api.del_file(self.repo_id, self.parent_dir,
self.folder_name, self.username)
def get_trash_dir_commit_id(self):
deleted_file = seafile_api.get_deleted(self.repo_id, 0, '/', None)
return deleted_file[0].commit_id
def get_lib_folder_name(self):
url = reverse('list_lib_dir', args=[self.repo_id])
resp = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
json_resp = json.loads(resp.content)
if len(json_resp['dirent_list']) == 0:
return None
return json_resp['dirent_list'][0]['obj_name']
def test_can_revert_dir(self):
self.login_as(self.user)
# check file exist when init
assert self.get_lib_folder_name() == self.folder_name
# delete
self.delete_dir()
# check file not exist after delete
assert self.get_lib_folder_name() == None
# get commit_id of deleted file
commit_id = self.get_trash_dir_commit_id()
resp = self.client.put(self.url,
"p=%s&commit_id=%s" % (self.folder_path, commit_id),
'application/x-www-form-urlencoded',
)
self.assertEqual(200, resp.status_code)
# check file has been reverted
assert self.get_lib_folder_name() == self.folder_name

View File

@ -0,0 +1,67 @@
import os
import json
from seaserv import seafile_api
from django.core.urlresolvers import reverse
from seahub.test_utils import BaseTestCase
class FileRevertTest(BaseTestCase):
def setUp(self):
self.repo_id = self.repo.id
self.file_path = self.file
self.parent_dir = os.path.dirname(self.file_path)
self.file_name = os.path.basename(self.file_path)
self.username = self.user.username
self.url = reverse('api2-file-revert', args=[self.repo_id])
def tearDown(self):
self.remove_repo()
def delete_file(self):
seafile_api.del_file(self.repo_id, self.parent_dir,
self.file_name, self.username)
def get_trash_file_commit_id(self):
deleted_file = seafile_api.get_deleted(self.repo_id, 0, '/', None)
return deleted_file[0].commit_id
def get_lib_file_name(self):
url = reverse('list_lib_dir', args=[self.repo_id])
resp = self.client.get(url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
json_resp = json.loads(resp.content)
if len(json_resp['dirent_list']) == 0:
return None
return json_resp['dirent_list'][0]['obj_name']
def test_can_revert_file(self):
self.login_as(self.user)
# check file exist when init
assert self.get_lib_file_name() == self.file_name
# delete
self.delete_file()
# check file not exist after delete
assert self.get_lib_file_name() == None
# get commit_id of deleted file
commit_id = self.get_trash_file_commit_id()
resp = self.client.put(self.url,
"p=%s&commit_id=%s" % (self.file_path, commit_id),
'application/x-www-form-urlencoded',
)
self.assertEqual(200, resp.status_code)
# check file has been reverted
assert self.get_lib_file_name() == self.file_name
def test_can_revert_unicode_filename(self):
# todo
pass

View File

@ -18,6 +18,8 @@ class RepoPublicTest(BaseTestCase):
def tearDown(self):
self.remove_repo(self.repo_id)
# clear cache between every test case to avoid config option cache issue
self.clear_cache()
def test_admin_can_set_pub_repo(self):
self.login_as(self.admin)

View File

@ -16,6 +16,7 @@ class SharedRepoTest(BaseTestCase):
def tearDown(self):
self.remove_repo(self.repo_id)
self.clear_cache()
def test_admin_can_share_repo_to_public(self):
self.login_as(self.admin)

View File

@ -1,6 +1,7 @@
from seahub.test_utils import BaseTestCase
from seahub.options.models import (UserOptions, KEY_USER_GUIDE,
VAL_USER_GUIDE_ON, VAL_USER_GUIDE_OFF)
VAL_USER_GUIDE_ON, VAL_USER_GUIDE_OFF,
KEY_DEFAULT_REPO)
class UserOptionsManagerTest(BaseTestCase):
def test_is_user_guide_enabled(self):
@ -25,3 +26,28 @@ class UserOptionsManagerTest(BaseTestCase):
assert UserOptions.objects.is_user_guide_enabled(self.user.email) is True
assert len(UserOptions.objects.filter(email=self.user.email,
option_key=KEY_USER_GUIDE)) == 1
def test_get_default_repo(self):
assert len(UserOptions.objects.filter(email=self.user.email, option_key=KEY_DEFAULT_REPO)) == 0
UserOptions.objects.create(email=self.user.email,
option_key=KEY_DEFAULT_REPO,
option_val=self.repo.id)
assert len(UserOptions.objects.filter(email=self.user.email, option_key=KEY_DEFAULT_REPO)) == 1
assert UserOptions.objects.get_default_repo(self.user.email) is not None
def test_get_default_repo_with_multiple_records(self):
assert len(UserOptions.objects.filter(email=self.user.email, option_key=KEY_DEFAULT_REPO)) == 0
UserOptions.objects.create(email=self.user.email,
option_key=KEY_DEFAULT_REPO,
option_val=self.repo.id)
UserOptions.objects.create(email=self.user.email,
option_key=KEY_DEFAULT_REPO,
option_val=self.repo.id)
assert len(UserOptions.objects.filter(email=self.user.email, option_key=KEY_DEFAULT_REPO)) == 2
assert UserOptions.objects.get_default_repo(self.user.email) is not None
assert len(UserOptions.objects.filter(email=self.user.email, option_key=KEY_DEFAULT_REPO)) == 1

View File

@ -0,0 +1,35 @@
from seahub.base.templatetags.seahub_tags import email2nickname
from seahub.profile.models import Profile
from seahub.test_utils import BaseTestCase
from tests.common.utils import randstring
class UpdateNicknameCacheTest(BaseTestCase):
def setUp(self):
self.tmp_user = self.create_user('user_%s@test.com' % randstring(4),
is_staff=False)
assert len(Profile.objects.all()) == 0
def tearDown(self):
self.remove_user(self.tmp_user.username)
def test_update_when_call_object_method(self):
username = self.tmp_user.username
assert email2nickname(username) == username.split('@')[0]
Profile.objects.add_or_update(username, 'nickname')
assert email2nickname(username) == 'nickname'
def test_updated_when_call_save(self):
username = self.tmp_user.username
assert email2nickname(username) == username.split('@')[0]
p = Profile.objects.get_profile_by_user(username)
if p is None:
p = Profile(user=username)
p.nickname = 'nickname'
p.save()
assert email2nickname(username) == 'nickname'

View File

@ -0,0 +1,33 @@
from django.core.urlresolvers import reverse
from seahub.base.templatetags.seahub_tags import email2nickname
from seahub.profile.models import Profile
from seahub.test_utils import BaseTestCase
from tests.common.utils import randstring
class EditProfileTest(BaseTestCase):
def setUp(self):
self.tmp_user = self.create_user('user_%s@test.com' % randstring(4),
is_staff=False)
assert len(Profile.objects.all()) == 0
self.url = reverse('edit_profile')
self.login_as(self.tmp_user)
def tearDown(self):
self.remove_user(self.tmp_user.username)
def test_can_render_edit_page(self):
resp = self.client.get(self.url)
self.assertEqual(200, resp.status_code)
self.assertTemplateUsed(resp, 'profile/set_profile.html')
def test_can_edit(self):
assert email2nickname(self.tmp_user.username) == self.tmp_user.username.split('@')[0]
resp = self.client.post(self.url, {
'nickname': 'new nickname'
})
self.assertEqual(302, resp.status_code)
self.assertRegexpMatches(resp['Location'], r'http://testserver/profile/')
assert email2nickname(self.tmp_user.username) == 'new nickname'

View File

@ -27,6 +27,8 @@ class LibrariesTest(BaseTestCase):
assert UserOptions.objects.is_user_guide_enabled(username) is False
def test_pub_repo_creation_config(self):
self.clear_cache()
# user
self.login_as(self.user)