1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-08-08 02:23:44 +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; } td { color: #333; font-size:14px; }
table img { vertical-align:middle; } table img { vertical-align:middle; }
p { margin:0.5em 0; } p { margin:0.5em 0; }
:focus {/* to overwrite user agent stylesheet */
outline:none;
}
/********** common class ***********/ /********** common class ***********/
.hl { background-color: #f8f8f8; }/*highlight*/ .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 * 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.intro = note
profile.save() profile.save()
refresh_profile_cache(email)
def _update_account_quota(self, request, email): def _update_account_quota(self, request, email):
storage = request.data.get("storage", None) 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/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()),
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})/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/$', 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/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/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/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/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'^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'^starredfiles/', StarredFileView.as_view(), name='starredfiles'),
url(r'^devices/', DevicesView.as_view(), name='api2-devices'), 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') send_file_access_msg(request, repo, path, 'api')
return Response(wopi_dict) return Response(wopi_dict)
class DevicesView(APIView): class DevicesView(APIView):
"""List user devices""" """List user devices"""
authentication_classes = (TokenAuthentication, SessionAuthentication) authentication_classes = (TokenAuthentication, SessionAuthentication)
@ -1894,6 +1895,7 @@ class DevicesView(APIView):
return Response({'success': True}) return Response({'success': True})
class FileView(APIView): class FileView(APIView):
""" """
Support uniform interface for file related operations, Support uniform interface for file related operations,
@ -2268,38 +2270,44 @@ class FileDetailView(APIView):
content_type=json_content_type) content_type=json_content_type)
class FileRevert(APIView): class FileRevert(APIView):
authentication_classes = (TokenAuthentication, ) authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle, ) throttle_classes = (UserRateThrottle, )
def put(self, request, repo_id, format=None): 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: 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 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: try:
ret = seafserv_threaded_rpc.revert_file(repo_id, commit_id, seafile_api.revert_file(repo_id, commit_id, path, username)
path, username)
except SearpcError as e: except SearpcError as e:
logger.error(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): class FileRevision(APIView):
authentication_classes = (TokenAuthentication, ) authentication_classes = (TokenAuthentication, )
@ -2698,6 +2706,45 @@ class DirDownloadView(APIView):
return HttpResponse(json.dumps(redirect_url), status=200, return HttpResponse(json.dumps(redirect_url), status=200,
content_type=json_content_type) 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): class DirShareView(APIView):
authentication_classes = (TokenAuthentication, ) authentication_classes = (TokenAuthentication, )
permission_classes = (IsAuthenticated,) 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: if autoescape and not safe_input:
lead, trail = escape(lead), escape(trail) lead, trail = escape(lead), escape(trail)
url, trimmed = escape(url), escape(trimmed) 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)) words[i] = mark_safe('%s%s%s' % (lead, middle, trail))
else: else:
if safe_input: if safe_input:

View File

@ -187,12 +187,18 @@ class UserOptionsManager(models.Manager):
- `self`: - `self`:
- `username`: - `username`:
""" """
try: user_options = super(UserOptionsManager, self).filter(
user_option = super(UserOptionsManager, self).get( email=username, option_key=KEY_DEFAULT_REPO)
email=username, option_key=KEY_DEFAULT_REPO)
return user_option.option_val if len(user_options) == 0:
except UserOptions.DoesNotExist:
return None 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): def passwd_change_required(self, username):
"""Check whether user need to change password. """Check whether user need to change password.
@ -211,10 +217,10 @@ class UserOptionsManager(models.Manager):
def unset_force_passwd_change(self, username): def unset_force_passwd_change(self, username):
return self.unset_user_option(username, KEY_FORCE_PASSWD_CHANGE) return self.unset_user_option(username, KEY_FORCE_PASSWD_CHANGE)
class UserOptions(models.Model): class UserOptions(models.Model):
email = LowerCaseCharField(max_length=255, db_index=True) email = LowerCaseCharField(max_length=255, db_index=True)
option_key = models.CharField(max_length=50) option_key = models.CharField(max_length=50)
option_val = models.CharField(max_length=50) option_val = models.CharField(max_length=50)
objects = UserOptionsManager() objects = UserOptionsManager()

View File

@ -129,11 +129,19 @@ class DetailedProfile(models.Model):
telephone = models.CharField(max_length=100) telephone = models.CharField(max_length=100)
objects = DetailedProfileManager() objects = DetailedProfileManager()
########## signal handler
########## signal handlers
from django.db.models.signals import post_save
from .utils import refresh_cache
@receiver(user_registered) @receiver(user_registered)
def clean_email_id_cache(sender, **kwargs): def clean_email_id_cache(sender, **kwargs):
from seahub.utils import normalize_cache_key from seahub.utils import normalize_cache_key
user = kwargs['user'] user = kwargs['user']
key = normalize_cache_key(user.email, EMAIL_ID_CACHE_PREFIX) key = normalize_cache_key(user.email, EMAIL_ID_CACHE_PREFIX)
cache.set(key, user.id, EMAIL_ID_CACHE_TIMEOUT) 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(): if form.is_valid():
form.save(username=username) form.save(username=username)
messages.success(request, _(u'Successfully edited profile.')) messages.success(request, _(u'Successfully edited profile.'))
# refresh nickname cache
refresh_cache(request.user.username)
return HttpResponseRedirect(reverse('edit_profile')) return HttpResponseRedirect(reverse('edit_profile'))
else: else:
messages.error(request, _(u'Failed to edit profile')) 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 %} {% load url from future %}
<!DOCTYPE html> <!DOCTYPE html>
@ -21,7 +21,7 @@
{% block info_bar_message %} {% block info_bar_message %}
{% if request.user.is_authenticated and request.cur_note %} {% if request.user.is_authenticated and request.cur_note %}
<div id="info-bar"> <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> <span class="close sf2-icon-x1 op-icon" data="{{ request.cur_note.id }}" title="{% trans "Close" %}"></span>
</div> </div>
{% endif %} {% endif %}
@ -149,7 +149,10 @@
{% include 'footer.html' %} {% include 'footer.html' %}
</div><!-- wrapper --> </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" src="{{ MEDIA_URL }}js/base.js?t=1404370380"></script>
<script type="text/javascript"> <script type="text/javascript">
$.jstree._themes = '{{ MEDIA_URL }}js/themes/'; $.jstree._themes = '{{ MEDIA_URL }}js/themes/';

View File

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

View File

@ -125,12 +125,31 @@ var get_more_trash = function(current_scan_stat) {
}); });
} }
$('table').on("click", ".restore-file, .restore-dir", function() { $('table').on("click", ".restore-file, .restore-dir", function() {
$('<form>', { var _this = $(this),
"method": 'POST', commit_id = _this.data('commit_id'),
"action": $(this).data('url'), path = _this.data('path');
"html": '<input name="csrfmiddlewaretoken" value="' + getCookie('csrftoken') + '" type="hidden">'
}).appendTo(document.body).submit(); $.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; return false;
}); });

View File

@ -38,7 +38,7 @@ $('#add-file-popup .submit').click(function() {
$.modal.close(); $.modal.close();
var files = ''; var files = '';
for (var i = 0, len = selected.length; i < len; i++) { 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'); files_ct.data('files', selected).html(files).removeClass('hide');
$('.rm', files_ct).click(function() { $('.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"> <div id="file-share" class="hide">
<h3 class="hd">{% trans 'Share %(name)s' %}</h3> <h3 class="hd">{% trans 'Share %(name)s' %}</h3>
<div id="file-share-tabs" class="left-right-tabs ovhd"> <div id="file-share-tabs" class="left-right-tabs ovhd">
<ul class="left-right-tabs-nav fleft"> <ul class="left-right-tabs-nav fleft">
<li class="tab"><a href="#link-share" class="a">{% trans "Download Link" %}</a></li> <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> </ul>
<div class="fright"> <div class="fright">
@ -58,51 +52,6 @@
</div> </div>
</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>
</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><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>{{ dirent.delete_time|translate_seahub_time }}</td>
<td></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 %} {% 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><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> <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><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.delete_time|translate_seahub_time }}</td>
<td>{{ dirent.file_size|filesizeformat }}</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 %} {% 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><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> <td></td>
@ -33,4 +33,3 @@
</tr> </tr>
{% endif %} {% endif %}
{% endfor %} {% endfor %}

View File

@ -1,7 +1,8 @@
{% load i18n %} {% load i18n %}
{% load url from future %} {% load url from future %}
var share_list = [], contacts = []; /*
var share_list = [];
$(function () { $(function () {
$.ajax({ $.ajax({
url:'{% url 'get_contacts' %}', url:'{% url 'get_contacts' %}',
@ -12,69 +13,21 @@ $(function () {
for (var i = 0, len = contact_list.length; i < len; i++) { for (var i = 0, len = contact_list.length; i < len; i++) {
contact_email = contact_list[i].email; contact_email = contact_list[i].email;
share_list.push({value: contact_email, label: contact_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) { function showSharePopup(op, name, aj_data, type, cur_path) {
var path; var path = cur_path + name;
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 form = $('#file-share'); var form = $('#file-share');
form.modal({appendTo: "#main",'focus':false, containerCss:{"padding":0}});
{% 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
});
var hd = $('#file-share .hd'); var hd = $('#file-share .hd');
hd.html(hd.html().replace('%(name)s', '<span class="op-target">' + HTMLescape(trimFilename(name, 30)) + '</span>')); 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(); $("#file-share-tabs").tabs();
// share link // 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_name"]').val(name);
$('input[name="file_shared_type"]').val(type); $('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'}); $('#simplemodal-container').css({'height':'auto', 'width':'auto'});
} }
@ -161,7 +59,7 @@ $('#send-link').click(function() {
var text = $('#download-extra-msg-text'); 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'))}); 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'); $('#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() { $("#link-send-form .cancel").click(function() {
@ -241,8 +139,8 @@ $('#gen-link-btn').click(function() {
obj = gen_link_btn.data('obj'), obj = gen_link_btn.data('obj'),
form = $('#link-options'), form = $('#link-options'),
form_id = form.attr('id'), form_id = form.attr('id'),
use_passwd = $('#link-passwd-switch').attr('checked'), use_passwd = $('#link-passwd-switch').prop('checked'),
set_expiration = $('#link-expire-switch').attr('checked'), set_expiration = $('#link-expire-switch').prop('checked'),
passwd, passwd_again, expire_days, post_data; passwd, passwd_again, expire_days, post_data;
if (use_passwd) { 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 () { $('#link-passwd-switch').click(function () {
var form = $('#link-options'), var form = $('#link-options'),
pwd_input = $('input[type="password"]', form); pwd_input = $('input[type="password"]', form);
var link_passwd = $('#link-passwd'); var link_passwd = $('#link-passwd');
if ($(this).attr('checked')) { if ($(this).prop('checked')) {
pwd_input.attr('disabled', false).removeClass('input-disabled'); pwd_input.attr('disabled', false).removeClass('input-disabled');
link_passwd.slideDown(100); link_passwd.slideDown(100);
} else { } else {
@ -532,7 +249,7 @@ $('#link-expire-switch').click(function () {
days_input = $('input[name="expire-days"]', form); days_input = $('input[name="expire-days"]', form);
var link_expire = $('#link-expire'); var link_expire = $('#link-expire');
if ($(this).attr('checked')) { if ($(this).prop('checked')) {
link_expire.slideDown(100); link_expire.slideDown(100);
days_input.attr('disabled', false).removeClass('input-disabled'); days_input.attr('disabled', false).removeClass('input-disabled');
} else { } else {

View File

@ -49,51 +49,43 @@
{% block extra_script %} {% block extra_script %}
<script type="text/javascript"> <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() { $('#add-admin-btn').click(function() {
var form = $("#add-admin-form"); var form = $("#add-admin-form");
form.modal({appendTo: "#main", focus:false}); form.modal({appendTo: "#main", focus:false});
$('#simplemodal-container').css({'height':'auto', 'padding':0}); $('#simplemodal-container').css({'height':'auto', 'padding':0});
$('#add-admin-tabs').tabs(); $('#add-admin-tabs').tabs();
addAutocomplete('#added-member-name', '#enter', user_list);
}); });
$('#add-admin-form').submit(function() { $('#add-admin-form').submit(function() {
var form = $(this), var $form = $(this),
cur_tab_id = $('.ui-tabs-selected a', form).attr('href'), emails = $.trim($('[name="user_email"]', $form).val());
post_data = '',
input = $('[name="user_email"]', form);
post_data = input.val();
if (!post_data) { if (!emails) {
apply_form_error(form.attr('id'), '{% trans "Please enter emails, or select some." %}');
return false; return false;
} }
var submit_btn = $('[type="submit"]', form); var $submitBtn = $('[type="submit"]', $form);
disable(submit_btn); disable($submitBtn);
$.ajax({ $.ajax({
url: '{% url 'batch_user_make_admin' %}', url: '{% url 'batch_user_make_admin' %}',
type: 'POST', type: 'POST',
dataType: 'json', dataType: 'json',
cache: false, cache: false,
beforeSend: prepareCSRFToken, beforeSend: prepareCSRFToken,
data: { data: {
'set_admin_emails': post_data 'set_admin_emails': emails
}, },
success: function(data) { success: function() {
location.reload('true'); location.reload(true);
}, },
error: function(data, textStatus, jqXHR) { error: function(xhr) {
var errors = $.parseJSON(data.responseText); var error_msg;
$.each(errors, function(index, value) { if (xhr.responseText) {
apply_form_error(form.attr('id'), value); error_msg = $.parseJSON(xhr.responseText).error;
}); } else {
enable(submit_btn); 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 * from .settings import *
# no cache for testing # no cache for testing
CACHES = { # CACHES = {
'default': { # 'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache', # 'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
} # }
} # }
# enlarge api throttle # enlarge api throttle
REST_FRAMEWORK = { REST_FRAMEWORK = {
@ -15,3 +15,7 @@ REST_FRAMEWORK = {
'user': '300/minute', '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 import os
from uuid import uuid4 from uuid import uuid4
from django.core.cache import cache
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.conf import settings from django.conf import settings
from django.http import SimpleCookie from django.http import SimpleCookie
@ -161,3 +162,8 @@ class BaseTestCase(TestCase, Fixtures):
if session_cookie: if session_cookie:
session.delete(session_key=session_cookie.value) session.delete(session_key=session_cookie.value)
self.client.cookies = SimpleCookie() 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): def tearDown(self):
self.remove_repo(self.repo_id) 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): def test_admin_can_set_pub_repo(self):
self.login_as(self.admin) self.login_as(self.admin)

View File

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

View File

@ -1,6 +1,7 @@
from seahub.test_utils import BaseTestCase from seahub.test_utils import BaseTestCase
from seahub.options.models import (UserOptions, KEY_USER_GUIDE, 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): class UserOptionsManagerTest(BaseTestCase):
def test_is_user_guide_enabled(self): 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 UserOptions.objects.is_user_guide_enabled(self.user.email) is True
assert len(UserOptions.objects.filter(email=self.user.email, assert len(UserOptions.objects.filter(email=self.user.email,
option_key=KEY_USER_GUIDE)) == 1 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 assert UserOptions.objects.is_user_guide_enabled(username) is False
def test_pub_repo_creation_config(self): def test_pub_repo_creation_config(self):
self.clear_cache()
# user # user
self.login_as(self.user) self.login_as(self.user)