From 8ae272847533e0f43aa8b8293801c34077556fa4 Mon Sep 17 00:00:00 2001 From: llj Date: Thu, 24 Apr 2014 13:45:41 +0800 Subject: [PATCH 01/17] [seaf] fixed bug for 'xss' handle --- seahub/templates/snippets/xss.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seahub/templates/snippets/xss.html b/seahub/templates/snippets/xss.html index 9b44838e7c..895e571644 100644 --- a/seahub/templates/snippets/xss.html +++ b/seahub/templates/snippets/xss.html @@ -1,5 +1,5 @@ function cleanHtmlForXss(html) { - var tag_whitelist = /^(<\/?(h1|h2|h3|h4|h5|h6|p|ul|ol|li|dl|dt|dd|pre|blockquote|code|q|table|thead|tbody|tfoot|tr|th|td|em|strong|del|ins|sup|sub|s)>||<\/a>|||<(br|hr)\s?\/?>)$/i; // is for with alignment in md extra syntax + var tag_whitelist = /^(<\/?(h1|h2|h3|h4|h5|h6|p|ul|ol|li|dl|dt|dd|pre|blockquote|code|q|cite|table|thead|tbody|tfoot|tr|th|td|em|strong|del|ins|sup|sub|s|div|span|a)(\s+.*)?>||<(br|hr)\s?\/?>)$/i; var str = html.replace(/<[^>]*>?/gim, function(tag){ var s; From 5ea7cb52ed50a4a1fca8918760cba17c736de3dc Mon Sep 17 00:00:00 2001 From: llj Date: Fri, 25 Apr 2014 13:39:54 +0800 Subject: [PATCH 02/17] [all_msg_list] msg form: fixed contact avatar ui bug --- media/css/seahub.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/media/css/seahub.css b/media/css/seahub.css index 8ca24530a8..61c6f96e6b 100644 --- a/media/css/seahub.css +++ b/media/css/seahub.css @@ -2060,6 +2060,10 @@ textarea:-moz-placeholder {/* for FF */ left:-64px; top:-1px; } +#send-msg-form .avatar { + border-radius:3px; + position:static; +} .msg-form .ops { text-align:right; margin-top:6px; From 98eb478a53c919ef41b5a8422f59f9e9af92c437 Mon Sep 17 00:00:00 2001 From: zhengxie Date: Sat, 26 Apr 2014 20:29:59 +0800 Subject: [PATCH 03/17] Remove avatar alt, and escape user nickname in notification list --- seahub/avatar/templatetags/avatar_tags.py | 15 ++++----------- seahub/notifications/models.py | 13 +++++++------ 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/seahub/avatar/templatetags/avatar_tags.py b/seahub/avatar/templatetags/avatar_tags.py index 241d8def63..b59a6f1eb8 100644 --- a/seahub/avatar/templatetags/avatar_tags.py +++ b/seahub/avatar/templatetags/avatar_tags.py @@ -53,18 +53,14 @@ def avatar(user, size=AVATAR_DEFAULT_SIZE): if not isinstance(user, User): try: user = User.objects.get(email=user) - alt = email2nickname(user.username) url = avatar_url(user, size) except User.DoesNotExist: url = get_default_avatar_non_registered_url() - alt = _("Default Avatar") except Exception as e: # Catch exceptions to avoid 500 errors. logger.error(e) url = get_default_avatar_non_registered_url() - alt = _("Default Avatar") else: - alt = email2nickname(user.username) try: url = avatar_url(user, size) except Exception as e: @@ -72,8 +68,7 @@ def avatar(user, size=AVATAR_DEFAULT_SIZE): logger.error(e) url = get_default_avatar_non_registered_url() - return """%s""" % (url, alt, - size, size) + return """""" % (url, size, size) @cache_result @register.simple_tag @@ -84,15 +79,13 @@ def primary_avatar(user, size=AVATAR_DEFAULT_SIZE): work for us. If that special view is then cached by a CDN for instance, we will avoid many db calls. """ - alt = unicode(user) url = reverse('avatar_render_primary', kwargs={'user' : user, 'size' : size}) - return """%s""" % (url, alt, - size, size) + return """""" % (url, size, size) @cache_result @register.simple_tag def render_avatar(avatar, size=AVATAR_DEFAULT_SIZE): if not avatar.thumbnail_exists(size): avatar.create_thumbnail(size) - return """%s""" % ( - avatar.avatar_url(size), str(avatar), size, size) + return """""" % ( + avatar.avatar_url(size), size, size) diff --git a/seahub/notifications/models.py b/seahub/notifications/models.py index daaef323f6..6ce79fea79 100644 --- a/seahub/notifications/models.py +++ b/seahub/notifications/models.py @@ -7,6 +7,7 @@ from django.db import models from django.db.models.signals import post_save from django.forms import ModelForm, Textarea from django.utils.http import urlquote +from django.utils.html import escape from django.utils.translation import ugettext as _ import seaserv @@ -471,7 +472,7 @@ class UserNotification(models.Model): return None msg = _(u"%(user)s has shared a library named %(repo_name)s to you.") % { - 'user': share_from, + 'user': escape(share_from), 'href': reverse('repo', args=[repo.id]), 'repo_name': repo.name } @@ -489,7 +490,7 @@ class UserNotification(models.Model): priv_share_token = d['priv_share_token'] msg = _(u"%(user)s has shared a file named %(file_name)s to you.") % { - 'user': share_from, + 'user': escape(share_from), 'href': reverse('view_priv_shared_file', args=[priv_share_token]), 'file_name': file_name } @@ -505,7 +506,7 @@ class UserNotification(models.Model): nickname = email2nickname(msg_from) msg = _(u"You have received a new message from %(user)s.") % { - 'user': nickname, + 'user': escape(nickname), 'href': reverse('user_msg_list', args=[msg_from]), } return msg @@ -536,7 +537,7 @@ class UserNotification(models.Model): else: msg = _(u"%(user)s posted a new discussion in %(group_name)s") % { 'href': reverse('group_discuss', args=[group.id]), - 'user': msg_from, + 'user': escape(msg_from), 'group_name': group.group_name} return msg @@ -561,7 +562,7 @@ class UserNotification(models.Model): } else: msg = _(u"%(user)s replied your group discussion") % { - 'user': reply_from, + 'user': escape(reply_from), 'href': reverse('msg_reply_new') } return msg @@ -588,7 +589,7 @@ class UserNotification(models.Model): 'username': username, 'href': reverse('group_members', args=[group_id]), 'group_name': group.group_name, - 'join_request_msg': join_request_msg, + 'join_request_msg': escape(join_request_msg), } return msg From b1b01a536430b222fa08fa8a4be659d3110a41e8 Mon Sep 17 00:00:00 2001 From: zhengxie Date: Sun, 27 Apr 2014 10:42:37 +0800 Subject: [PATCH 04/17] Clean repo name when rename repo, escape nickname, desc before save and clean code --- seahub/forms.py | 8 ++++++ seahub/profile/forms.py | 14 +++++----- seahub/views/__init__.py | 58 ---------------------------------------- 3 files changed, 14 insertions(+), 66 deletions(-) diff --git a/seahub/forms.py b/seahub/forms.py index 9ddba2287b..2e62c3d51d 100644 --- a/seahub/forms.py +++ b/seahub/forms.py @@ -174,6 +174,14 @@ class RepoSettingForm(forms.Form): days = forms.IntegerField(required=False, error_messages={'invalid': _('Please enter a number')}) + def clean_repo_name(self): + repo_name = self.cleaned_data['repo_name'] + if not is_valid_filename(repo_name): + error_msg = _(u"Name %s is not valid") % repo_name + raise forms.ValidationError(error_msg) + else: + return repo_name + class SharedLinkPasswordForm(forms.Form): """ Form for user to access shared files/directory. diff --git a/seahub/profile/forms.py b/seahub/profile/forms.py index 1f2d19dd2f..1cd8df123b 100644 --- a/seahub/profile/forms.py +++ b/seahub/profile/forms.py @@ -1,6 +1,6 @@ # encoding: utf-8 from django import forms -from django.utils.translation import ugettext_lazy as _ +from django.utils.html import escape from seahub.profile.models import Profile, DetailedProfile @@ -9,18 +9,16 @@ class ProfileForm(forms.Form): intro = forms.CharField(max_length=256, required=False) def save(self, username): - nickname = self.cleaned_data['nickname'] - intro = self.cleaned_data['intro'] + nickname = escape(self.cleaned_data['nickname']) + intro = escape(self.cleaned_data['intro']) Profile.objects.add_or_update(username, nickname, intro) - - + class DetailedProfileForm(ProfileForm): department = forms.CharField(max_length=512, required=False) telephone = forms.CharField(max_length=100, required=False) def save(self, username): super(DetailedProfileForm, self).save(username) - department = self.cleaned_data['department'] - telephone = self.cleaned_data['telephone'] + department = escape(self.cleaned_data['department']) + telephone = escape(self.cleaned_data['telephone']) DetailedProfile.objects.add_or_update(username, department, telephone) - diff --git a/seahub/views/__init__.py b/seahub/views/__init__.py index a5e03e1f4b..7400c1b766 100644 --- a/seahub/views/__init__.py +++ b/seahub/views/__init__.py @@ -435,64 +435,6 @@ def repo_recycle_view(request, repo_id): else: return render_recycle_dir(request, repo_id, commit_id) -@login_required -def repo_save_settings(request): - if request.method != 'POST': - raise Http404 - - username = request.user.username - content_type = 'application/json; charset=utf-8' - - form = RepoSettingForm(request.POST) - if form.is_valid(): - repo_id = form.cleaned_data['repo_id'] - repo_name = form.cleaned_data['repo_name'] - repo_desc = form.cleaned_data['repo_desc'] - days = form.cleaned_data['days'] - repo_owner = form.cleaned_data['repo_owner'] - - repo = get_repo(repo_id) - if not repo: - err_msg = _(u'Library does not exist.') - return HttpResponse(json.dumps({'error': err_msg}), - status=400, content_type=content_type) - - # check permission - if request.user.org: - is_owner = True if is_org_repo_owner( - request.user.org['org_id'], repo_id, username) else False - else: - is_owner = True if is_repo_owner(username, repo_id) else False - if not is_owner: - err_msg = _(u'You do not have permission to perform this action.') - return HttpResponse(json.dumps({'error': err_msg}), - status=403, content_type=content_type) - - # Edit library info (name, descryption). - if repo.name != repo_name or repo.desc != repo_desc: - if not edit_repo(repo_id, repo_name, repo_desc, username): - err_msg = _(u'Failed to edit library information.') - return HttpResponse(json.dumps({'error': err_msg}), - status=500, content_type=content_type) - - # set library history - if days != None: - res = set_repo_history_limit(repo_id, days) - if res != 0: - return HttpResponse(json.dumps({'error': _(u'Failed to save settings on server')}), - status=400, content_type=content_type) - - # set library owner - if repo_owner is not None and repo_owner != username: - seafile_api.set_repo_owner(repo_id, repo_owner) - - messages.success(request, _(u'Settings saved.')) - return HttpResponse(json.dumps({'success': True}), - content_type=content_type) - else: - return HttpResponse(json.dumps({'error': str(form.errors.values()[0])}), - status=400, content_type=content_type) - @login_required def repo_settings(request, repo_id): """List and change library settings. From 9c53e59ee0c4e5418b54d47c932454b7b907dc03 Mon Sep 17 00:00:00 2001 From: zhengxie Date: Sun, 27 Apr 2014 11:29:31 +0800 Subject: [PATCH 05/17] Revert escape nickname, desc, etc in user profile --- seahub/profile/forms.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/seahub/profile/forms.py b/seahub/profile/forms.py index 1cd8df123b..a75219f6e2 100644 --- a/seahub/profile/forms.py +++ b/seahub/profile/forms.py @@ -1,6 +1,5 @@ # encoding: utf-8 from django import forms -from django.utils.html import escape from seahub.profile.models import Profile, DetailedProfile @@ -9,8 +8,8 @@ class ProfileForm(forms.Form): intro = forms.CharField(max_length=256, required=False) def save(self, username): - nickname = escape(self.cleaned_data['nickname']) - intro = escape(self.cleaned_data['intro']) + nickname = self.cleaned_data['nickname'] + intro = self.cleaned_data['intro'] Profile.objects.add_or_update(username, nickname, intro) class DetailedProfileForm(ProfileForm): @@ -19,6 +18,6 @@ class DetailedProfileForm(ProfileForm): def save(self, username): super(DetailedProfileForm, self).save(username) - department = escape(self.cleaned_data['department']) - telephone = escape(self.cleaned_data['telephone']) + department = self.cleaned_data['department'] + telephone = self.cleaned_data['telephone'] DetailedProfile.objects.add_or_update(username, department, telephone) From 85acd95e14b3fcbf2c1be942ff7df245ab1b129e Mon Sep 17 00:00:00 2001 From: llj Date: Sun, 27 Apr 2014 11:51:22 +0800 Subject: [PATCH 06/17] [repo_create, 'details' view] escape some strings --- media/js/base.js | 7 +++++++ seahub/templates/snippets/list_commit_detail.html | 2 +- seahub/templates/snippets/myhome_extra_script.html | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/media/js/base.js b/media/js/base.js index b224eef9a1..7af6422291 100644 --- a/media/js/base.js +++ b/media/js/base.js @@ -652,3 +652,10 @@ function addAnchorsToHeaders(html) { }); return tree.html(); } + +function HTMLescape(html){ + return document.createElement('div') + .appendChild(document.createTextNode(html)) + .parentNode + .innerHTML; +} diff --git a/seahub/templates/snippets/list_commit_detail.html b/seahub/templates/snippets/list_commit_detail.html index abec959e60..1c355f6d34 100644 --- a/seahub/templates/snippets/list_commit_detail.html +++ b/seahub/templates/snippets/list_commit_detail.html @@ -29,7 +29,7 @@ function listCommitDetails(url, t) { show(data['deldir'], "{% trans "Deleted directories" %}"); if (!con) { if (data['cmt_desc']) { - con = '

' + data['cmt_desc'] + '

'; + con = '

' + HTMLescape(data['cmt_desc']) + '

'; } } $('#ls-ch').css('text-align','left').html(heading + time + con); diff --git a/seahub/templates/snippets/myhome_extra_script.html b/seahub/templates/snippets/myhome_extra_script.html index 80f7dc5b95..845f6e159a 100644 --- a/seahub/templates/snippets/myhome_extra_script.html +++ b/seahub/templates/snippets/myhome_extra_script.html @@ -102,12 +102,12 @@ function repoCreateSuccessCallback(data) { $.modal.close(); var new_repo_item = $('#new-repo .repo-item').clone(true); var tds = $('td', new_repo_item); - var repo_id = data['repo_id'], repo_name = data['repo_name']; + var repo_id = data['repo_id'], repo_name = HTMLescape(data['repo_name']); if (data['repo_enc']) { $('img', $(tds[0])).attr('src', '{{MEDIA_URL}}img/sync-folder-encrypt-20.png'); } $(tds[1]).html('' + repo_name + ''); - $(tds[2]).html(data['repo_desc']); + $(tds[2]).text(data['repo_desc']); $(tds[4]).attr('data-id', repo_id).attr('data-name', repo_name); var my_own_repos = $('#my-own-repos'); From 284304dc265d89541f95572dc021a24f00d79b74 Mon Sep 17 00:00:00 2001 From: zhengxie Date: Mon, 28 Apr 2014 10:55:18 +0800 Subject: [PATCH 07/17] Add an option to check share link traffic --- seahub/utils/__init__.py | 51 +++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/seahub/utils/__init__.py b/seahub/utils/__init__.py index 9bde55ffee..8cd008d9fe 100644 --- a/seahub/utils/__init__.py +++ b/seahub/utils/__init__.py @@ -52,7 +52,12 @@ try: from seahub.settings import ENABLE_INNER_HTTPSERVER except ImportError: ENABLE_INNER_HTTPSERVER = True - + +try: + from seahub.settings import CHECK_SHARE_LINK_TRAFFIC +except ImportError: + CHECK_SHARE_LINK_TRAFFIC = False + from seahub.utils.file_types import * from seahub.utils.htmldiff import HtmlDiff @@ -888,30 +893,32 @@ if EVENTS_CONFIG_FILE and hasattr(seafevents, 'get_user_traffic_stat'): session.close() return stat - def user_traffic_over_limit(username): - """Return ``True`` if user traffic over the limit, otherwise ``False``. - """ - from seahub_extra.plan.models import UserPlan - from seahub_extra.plan.settings import PLAN - up = UserPlan.objects.get_valid_plan_by_user(username) - plan = 'Free' if up is None else up.plan_type - traffic_limit = int(PLAN[plan]['share_link_traffic']) * 1024 * 1024 * 1024 - - try: - stat = get_user_traffic_stat(username) - except Exception as e: - logger.error(e) - stat = None - - if stat is None: - return True - - month_traffic = stat['file_view'] + stat['file_download'] + stat['dir_download'] - return True if month_traffic >= traffic_limit else False else: def get_user_traffic_stat(username): pass def get_user_traffic_list(): pass - def user_traffic_over_limit(request): + +def user_traffic_over_limit(username): + """Return ``True`` if user traffic over the limit, otherwise ``False``. + """ + if not CHECK_SHARE_LINK_TRAFFIC: return False + + from seahub_extra.plan.models import UserPlan + from seahub_extra.plan.settings import PLAN + up = UserPlan.objects.get_valid_plan_by_user(username) + plan = 'Free' if up is None else up.plan_type + traffic_limit = int(PLAN[plan]['share_link_traffic']) * 1024 * 1024 * 1024 + + try: + stat = get_user_traffic_stat(username) + except Exception as e: + logger.error(e) + stat = None + + if stat is None: + return True + + month_traffic = stat['file_view'] + stat['file_download'] + stat['dir_download'] + return True if month_traffic >= traffic_limit else False From 2845fdd853218d1a1646b361f4b868bdcf27005a Mon Sep 17 00:00:00 2001 From: llj Date: Mon, 28 Apr 2014 13:35:08 +0800 Subject: [PATCH 08/17] [select2 bugfix] removed 'title' for selected item --- media/js/select2.min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/media/js/select2.min.js b/media/js/select2.min.js index d78d00fc40..8d49cae6a0 100644 --- a/media/js/select2.min.js +++ b/media/js/select2.min.js @@ -19,4 +19,4 @@ either express or implied. See the Apache License and the GPL License for the sp permissions and limitations under the Apache License and the GPL License. */ (function(a){a.fn.each2===void 0&&a.fn.extend({each2:function(b){for(var c=a([0]),d=-1,e=this.length;e>++d&&(c.context=c[0]=this[d])&&b.call(c[0],d,c)!==!1;);return this}})})(jQuery),function(a,b){"use strict";function m(a,b){for(var c=0,d=b.length;d>c;c+=1)if(o(a,b[c]))return c;return-1}function n(){var b=a(l);b.appendTo("body");var c={width:b.width()-b[0].clientWidth,height:b.height()-b[0].clientHeight};return b.remove(),c}function o(a,c){return a===c?!0:a===b||c===b?!1:null===a||null===c?!1:a.constructor===String?a+""==c+"":c.constructor===String?c+""==a+"":!1}function p(b,c){var d,e,f;if(null===b||1>b.length)return[];for(d=b.split(c),e=0,f=d.length;f>e;e+=1)d[e]=a.trim(d[e]);return d}function q(a){return a.outerWidth(!1)-a.width()}function r(c){var d="keyup-change-value";c.on("keydown",function(){a.data(c,d)===b&&a.data(c,d,c.val())}),c.on("keyup",function(){var e=a.data(c,d);e!==b&&c.val()!==e&&(a.removeData(c,d),c.trigger("keyup-change"))})}function s(c){c.on("mousemove",function(c){var d=i;(d===b||d.x!==c.pageX||d.y!==c.pageY)&&a(c.target).trigger("mousemove-filtered",c)})}function t(a,c,d){d=d||b;var e;return function(){var b=arguments;window.clearTimeout(e),e=window.setTimeout(function(){c.apply(d,b)},a)}}function u(a){var c,b=!1;return function(){return b===!1&&(c=a(),b=!0),c}}function v(a,b){var c=t(a,function(a){b.trigger("scroll-debounced",a)});b.on("scroll",function(a){m(a.target,b.get())>=0&&c(a)})}function w(a){a[0]!==document.activeElement&&window.setTimeout(function(){var d,b=a[0],c=a.val().length;a.focus(),a.is(":visible")&&b===document.activeElement&&(b.setSelectionRange?b.setSelectionRange(c,c):b.createTextRange&&(d=b.createTextRange(),d.collapse(!1),d.select()))},0)}function x(b){b=a(b)[0];var c=0,d=0;if("selectionStart"in b)c=b.selectionStart,d=b.selectionEnd-c;else if("selection"in document){b.focus();var e=document.selection.createRange();d=document.selection.createRange().text.length,e.moveStart("character",-b.value.length),c=e.text.length-d}return{offset:c,length:d}}function y(a){a.preventDefault(),a.stopPropagation()}function z(a){a.preventDefault(),a.stopImmediatePropagation()}function A(b){if(!h){var c=b[0].currentStyle||window.getComputedStyle(b[0],null);h=a(document.createElement("div")).css({position:"absolute",left:"-10000px",top:"-10000px",display:"none",fontSize:c.fontSize,fontFamily:c.fontFamily,fontStyle:c.fontStyle,fontWeight:c.fontWeight,letterSpacing:c.letterSpacing,textTransform:c.textTransform,whiteSpace:"nowrap"}),h.attr("class","select2-sizer"),a("body").append(h)}return h.text(b.val()),h.width()}function B(b,c,d){var e,g,f=[];e=b.attr("class"),e&&(e=""+e,a(e.split(" ")).each2(function(){0===this.indexOf("select2-")&&f.push(this)})),e=c.attr("class"),e&&(e=""+e,a(e.split(" ")).each2(function(){0!==this.indexOf("select2-")&&(g=d(this),g&&f.push(this))})),b.attr("class",f.join(" "))}function C(a,c,d,e){var f=a.toUpperCase().indexOf(c.toUpperCase()),g=c.length;return 0>f?(d.push(e(a)),b):(d.push(e(a.substring(0,f))),d.push(""),d.push(e(a.substring(f,f+g))),d.push(""),d.push(e(a.substring(f+g,a.length))),b)}function D(c){var d,e=0,f=null,g=c.quietMillis||100,h=c.url,i=this;return function(j){window.clearTimeout(d),d=window.setTimeout(function(){e+=1;var d=e,g=c.data,k=h,l=c.transport||a.fn.select2.ajaxDefaults.transport,m={type:c.type||"GET",cache:c.cache||!1,jsonpCallback:c.jsonpCallback||b,dataType:c.dataType||"json"},n=a.extend({},a.fn.select2.ajaxDefaults.params,m);g=g?g.call(i,j.term,j.page,j.context):null,k="function"==typeof k?k.call(i,j.term,j.page,j.context):k,null!==f&&f.abort(),c.params&&(a.isFunction(c.params)?a.extend(n,c.params.call(i)):a.extend(n,c.params)),a.extend(n,{url:k,dataType:c.dataType,data:g,success:function(a){if(!(e>d)){var b=c.results(a,j.page);j.callback(b)}}}),f=l.call(i,n)},g)}}function E(c){var e,f,d=c,g=function(a){return""+a.text};a.isArray(d)&&(f=d,d={results:f}),a.isFunction(d)===!1&&(f=d,d=function(){return f});var h=d();return h.text&&(g=h.text,a.isFunction(g)||(e=h.text,g=function(a){return a[e]})),function(c){var h,e=c.term,f={results:[]};return""===e?(c.callback(d()),b):(h=function(b,d){var f,i;if(b=b[0],b.children){f={};for(i in b)b.hasOwnProperty(i)&&(f[i]=b[i]);f.children=[],a(b.children).each2(function(a,b){h(b,f.children)}),(f.children.length||c.matcher(e,g(f),b))&&d.push(f)}else c.matcher(e,g(b),b)&&d.push(b)},a(d().results).each2(function(a,b){h(b,f.results)}),c.callback(f),b)}}function F(c){var d=a.isFunction(c);return function(e){var f=e.term,g={results:[]};a(d?c():c).each(function(){var a=this.text!==b,c=a?this.text:this;(""===f||e.matcher(f,c))&&g.results.push(a?this:{id:this,text:this})}),e.callback(g)}}function G(b){if(a.isFunction(b))return!0;if(!b)return!1;throw Error("formatterName must be a function or a falsy value")}function H(b){return a.isFunction(b)?b():b}function I(b){var c=0;return a.each(b,function(a,b){b.children?c+=I(b.children):c++}),c}function J(a,c,d,e){var h,i,j,k,l,f=a,g=!1;if(!e.createSearchChoice||!e.tokenSeparators||1>e.tokenSeparators.length)return b;for(;;){for(i=-1,j=0,k=e.tokenSeparators.length;k>j&&(l=e.tokenSeparators[j],i=a.indexOf(l),!(i>=0));j++);if(0>i)break;if(h=a.substring(0,i),a=a.substring(i+l.length),h.length>0&&(h=e.createSearchChoice(h,c),h!==b&&null!==h&&e.id(h)!==b&&null!==e.id(h))){for(g=!1,j=0,k=c.length;k>j;j++)if(o(e.id(h),e.id(c[j]))){g=!0;break}g||d(h)}}return f!==a?a:b}function K(b,c){var d=function(){};return d.prototype=new b,d.prototype.constructor=d,d.prototype.parent=b.prototype,d.prototype=a.extend(d.prototype,c),d}if(window.Select2===b){var c,d,e,f,g,h,i,j,k,c={TAB:9,ENTER:13,ESC:27,SPACE:32,LEFT:37,UP:38,RIGHT:39,DOWN:40,SHIFT:16,CTRL:17,ALT:18,PAGE_UP:33,PAGE_DOWN:34,HOME:36,END:35,BACKSPACE:8,DELETE:46,isArrow:function(a){switch(a=a.which?a.which:a){case c.LEFT:case c.RIGHT:case c.UP:case c.DOWN:return!0}return!1},isControl:function(a){var b=a.which;switch(b){case c.SHIFT:case c.CTRL:case c.ALT:return!0}return a.metaKey?!0:!1},isFunctionKey:function(a){return a=a.which?a.which:a,a>=112&&123>=a}},l="
";j=a(document),g=function(){var a=1;return function(){return a++}}(),j.on("mousemove",function(a){i={x:a.pageX,y:a.pageY}}),d=K(Object,{bind:function(a){var b=this;return function(){a.apply(b,arguments)}},init:function(c){var d,e,h,i,f=".select2-results";this.opts=c=this.prepareOpts(c),this.id=c.id,c.element.data("select2")!==b&&null!==c.element.data("select2")&&this.destroy(),this.container=this.createContainer(),this.containerId="s2id_"+(c.element.attr("id")||"autogen"+g()),this.containerSelector="#"+this.containerId.replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g,"\\$1"),this.container.attr("id",this.containerId),this.body=u(function(){return c.element.closest("body")}),B(this.container,this.opts.element,this.opts.adaptContainerCssClass),this.container.css(H(c.containerCss)),this.container.addClass(H(c.containerCssClass)),this.elementTabIndex=this.opts.element.attr("tabindex"),this.opts.element.data("select2",this).attr("tabindex","-1").before(this.container),this.container.data("select2",this),this.dropdown=this.container.find(".select2-drop"),this.dropdown.addClass(H(c.dropdownCssClass)),this.dropdown.data("select2",this),this.results=d=this.container.find(f),this.search=e=this.container.find("input.select2-input"),this.resultsPage=0,this.context=null,this.initContainer(),s(this.results),this.dropdown.on("mousemove-filtered touchstart touchmove touchend",f,this.bind(this.highlightUnderEvent)),v(80,this.results),this.dropdown.on("scroll-debounced",f,this.bind(this.loadMoreIfNeeded)),a(this.container).on("change",".select2-input",function(a){a.stopPropagation()}),a(this.dropdown).on("change",".select2-input",function(a){a.stopPropagation()}),a.fn.mousewheel&&d.mousewheel(function(a,b,c,e){var f=d.scrollTop();e>0&&0>=f-e?(d.scrollTop(0),y(a)):0>e&&d.get(0).scrollHeight-d.scrollTop()+e<=d.height()&&(d.scrollTop(d.get(0).scrollHeight-d.height()),y(a))}),r(e),e.on("keyup-change input paste",this.bind(this.updateResults)),e.on("focus",function(){e.addClass("select2-focused")}),e.on("blur",function(){e.removeClass("select2-focused")}),this.dropdown.on("mouseup",f,this.bind(function(b){a(b.target).closest(".select2-result-selectable").length>0&&(this.highlightUnderEvent(b),this.selectHighlighted(b))})),this.dropdown.on("click mouseup mousedown",function(a){a.stopPropagation()}),a.isFunction(this.opts.initSelection)&&(this.initSelection(),this.monitorSource()),null!==c.maximumInputLength&&this.search.attr("maxlength",c.maximumInputLength);var h=c.element.prop("disabled");h===b&&(h=!1),this.enable(!h);var i=c.element.prop("readonly");i===b&&(i=!1),this.readonly(i),k=k||n(),this.autofocus=c.element.prop("autofocus"),c.element.prop("autofocus",!1),this.autofocus&&this.focus()},destroy:function(){var a=this.opts.element.data("select2");this.propertyObserver&&(delete this.propertyObserver,this.propertyObserver=null),a!==b&&(a.container.remove(),a.dropdown.remove(),a.opts.element.removeClass("select2-offscreen").removeData("select2").off(".select2").attr({tabindex:this.elementTabIndex}).prop("autofocus",this.autofocus||!1).show())},optionToData:function(a){return a.is("option")?{id:a.prop("value"),text:a.text(),element:a.get(),css:a.attr("class"),disabled:a.prop("disabled"),locked:o(a.attr("locked"),"locked")}:a.is("optgroup")?{text:a.attr("label"),children:[],element:a.get(),css:a.attr("class")}:b},prepareOpts:function(c){var d,e,f,g,h=this;if(d=c.element,"select"===d.get(0).tagName.toLowerCase()&&(this.select=e=c.element),e&&a.each(["id","multiple","ajax","query","createSearchChoice","initSelection","data","tags"],function(){if(this in c)throw Error("Option '"+this+"' is not allowed for Select2 when attached to a ","
"," ","
    ","
","
"].join(""));return b},enableInterface:function(){this.parent.enableInterface.apply(this,arguments)&&this.focusser.prop("disabled",!this.isInterfaceEnabled())},opening:function(){var b,c;this.parent.opening.apply(this,arguments),this.showSearchInput!==!1&&this.search.val(this.focusser.val()),this.search.focus(),b=this.search.get(0),b.createTextRange&&(c=b.createTextRange(),c.collapse(!1),c.select()),this.focusser.prop("disabled",!0).val(""),this.updateResults(!0),this.opts.element.trigger(a.Event("select2-open"))},close:function(){this.opened()&&(this.parent.close.apply(this,arguments),this.focusser.removeAttr("disabled"),this.focusser.focus())},focus:function(){this.opened()?this.close():(this.focusser.removeAttr("disabled"),this.focusser.focus())},isFocused:function(){return this.container.hasClass("select2-container-active")},cancel:function(){this.parent.cancel.apply(this,arguments),this.focusser.removeAttr("disabled"),this.focusser.focus()},initContainer:function(){var d,e=this.container,f=this.dropdown;this.showSearch(!1),this.selection=d=e.find(".select2-choice"),this.focusser=e.find(".select2-focusser"),this.focusser.attr("id","s2id_autogen"+g()),a("label[for='"+this.opts.element.attr("id")+"']").attr("for",this.focusser.attr("id")),this.focusser.attr("tabindex",this.elementTabIndex),this.search.on("keydown",this.bind(function(a){if(this.isInterfaceEnabled()){if(a.which===c.PAGE_UP||a.which===c.PAGE_DOWN)return y(a),b;switch(a.which){case c.UP:case c.DOWN:return this.moveHighlight(a.which===c.UP?-1:1),y(a),b;case c.ENTER:return this.selectHighlighted(),y(a),b;case c.TAB:return this.selectHighlighted({noFocus:!0}),b;case c.ESC:return this.cancel(a),y(a),b}}})),this.search.on("blur",this.bind(function(){document.activeElement===this.body().get(0)&&window.setTimeout(this.bind(function(){this.search.focus()}),0)})),this.focusser.on("keydown",this.bind(function(a){return!this.isInterfaceEnabled()||a.which===c.TAB||c.isControl(a)||c.isFunctionKey(a)||a.which===c.ESC?b:this.opts.openOnEnter===!1&&a.which===c.ENTER?(y(a),b):a.which==c.DOWN||a.which==c.UP||a.which==c.ENTER&&this.opts.openOnEnter?(this.open(),y(a),b):a.which==c.DELETE||a.which==c.BACKSPACE?(this.opts.allowClear&&this.clear(),y(a),b):b})),r(this.focusser),this.focusser.on("keyup-change input",this.bind(function(a){a.stopPropagation(),this.opened()||this.open()})),d.on("mousedown","abbr",this.bind(function(a){this.isInterfaceEnabled()&&(this.clear(),z(a),this.close(),this.selection.focus())})),d.on("mousedown",this.bind(function(b){this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.opened()?this.close():this.isInterfaceEnabled()&&this.open(),y(b)})),f.on("mousedown",this.bind(function(){this.search.focus()})),d.on("focus",this.bind(function(a){y(a)})),this.focusser.on("focus",this.bind(function(){this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.container.addClass("select2-container-active")})).on("blur",this.bind(function(){this.opened()||(this.container.removeClass("select2-container-active"),this.opts.element.trigger(a.Event("select2-blur")))})),this.search.on("focus",this.bind(function(){this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.container.addClass("select2-container-active")})),this.initContainerWidth(),this.opts.element.addClass("select2-offscreen"),this.setPlaceholder()},clear:function(a){var b=this.selection.data("select2-data");b&&(this.opts.element.val(""),this.selection.find("span").empty(),this.selection.removeData("select2-data"),this.setPlaceholder(),a!==!1&&(this.opts.element.trigger({type:"select2-removed",val:this.id(b),choice:b}),this.triggerChange({removed:b})))},initSelection:function(){if(""===this.opts.element.val()&&""===this.opts.element.text())this.updateSelection([]),this.close(),this.setPlaceholder();else{var c=this;this.opts.initSelection.call(null,this.opts.element,function(a){a!==b&&null!==a&&(c.updateSelection(a),c.close(),c.setPlaceholder())})}},prepareOpts:function(){var b=this.parent.prepareOpts.apply(this,arguments),c=this;return"select"===b.element.get(0).tagName.toLowerCase()?b.initSelection=function(a,b){var d=a.find(":selected");b(c.optionToData(d))}:"data"in b&&(b.initSelection=b.initSelection||function(c,d){var e=c.val(),f=null;b.query({matcher:function(a,c,d){var g=o(e,b.id(d));return g&&(f=d),g},callback:a.isFunction(d)?function(){d(f)}:a.noop})}),b},getPlaceholder:function(){return this.select&&""!==this.select.find("option").first().text()?b:this.parent.getPlaceholder.apply(this,arguments)},setPlaceholder:function(){var a=this.getPlaceholder();if(""===this.opts.element.val()&&a!==b){if(this.select&&""!==this.select.find("option:first").text())return;this.selection.find("span").html(this.opts.escapeMarkup(a)),this.selection.addClass("select2-default"),this.container.removeClass("select2-allowclear")}},postprocessResults:function(a,c,d){var e=0,f=this;if(this.findHighlightableChoices().each2(function(a,c){return o(f.id(c.data("select2-data")),f.opts.element.val())?(e=a,!1):b}),d!==!1&&this.highlight(e),c===!0&&this.showSearchInput===!1){var h=this.opts.minimumResultsForSearch;h>=0&&this.showSearch(I(a.results)>=h)}},showSearch:function(b){this.showSearchInput=b,this.dropdown.find(".select2-search").toggleClass("select2-search-hidden",!b),this.dropdown.find(".select2-search").toggleClass("select2-offscreen",!b),a(this.dropdown,this.container).toggleClass("select2-with-searchbox",b)},onSelect:function(a,b){if(this.triggerSelect(a)){var c=this.opts.element.val(),d=this.data();this.opts.element.val(this.id(a)),this.updateSelection(a),this.opts.element.trigger({type:"select2-selected",val:this.id(a),choice:a}),this.close(),b&&b.noFocus||this.selection.focus(),o(c,this.id(a))||this.triggerChange({added:a,removed:d})}},updateSelection:function(a){var d,c=this.selection.find("span");this.selection.data("select2-data",a),c.empty(),d=this.opts.formatSelection(a,c),d!==b&&c.append(this.opts.escapeMarkup(d)),this.selection.removeClass("select2-default"),this.opts.allowClear&&this.getPlaceholder()!==b&&this.container.addClass("select2-allowclear")},val:function(){var a,c=!1,d=null,e=this,f=this.data();if(0===arguments.length)return this.opts.element.val();if(a=arguments[0],arguments.length>1&&(c=arguments[1]),this.select)this.select.val(a).find(":selected").each2(function(a,b){return d=e.optionToData(b),!1}),this.updateSelection(d),this.setPlaceholder(),c&&this.triggerChange({added:d,removed:f});else{if(this.opts.initSelection===b)throw Error("cannot call val() if initSelection() is not defined");if(!a&&0!==a)return this.clear(c),b;this.opts.element.val(a),this.opts.initSelection(this.opts.element,function(a){e.opts.element.val(a?e.id(a):""),e.updateSelection(a),e.setPlaceholder(),c&&e.triggerChange({added:a,removed:f})})}},clearSearch:function(){this.search.val(""),this.focusser.val("")},data:function(a,c){var d;return 0===arguments.length?(d=this.selection.data("select2-data"),d==b&&(d=null),d):(a&&""!==a?(d=this.data(),this.opts.element.val(a?this.id(a):""),this.updateSelection(a),c&&this.triggerChange({added:a,removed:d})):this.clear(c),b)}}),f=K(d,{createContainer:function(){var b=a(document.createElement("div")).attr({"class":"select2-container select2-container-multi"}).html(["
    ","
  • "," ","
  • ","
","
","
    ","
","
"].join("")); -return b},prepareOpts:function(){var b=this.parent.prepareOpts.apply(this,arguments),c=this;return"select"===b.element.get(0).tagName.toLowerCase()?b.initSelection=function(a,b){var d=[];a.find(":selected").each2(function(a,b){d.push(c.optionToData(b))}),b(d)}:"data"in b&&(b.initSelection=b.initSelection||function(c,d){var e=p(c.val(),b.separator),f=[];b.query({matcher:function(c,d,g){var h=a.grep(e,function(a){return o(a,b.id(g))}).length;return h&&f.push(g),h},callback:a.isFunction(d)?function(){for(var a=[],c=0;e.length>c;c++)for(var g=e[c],h=0;f.length>h;h++){var i=f[h];if(o(g,b.id(i))){a.push(i),f.splice(h,1);break}}d(a)}:a.noop})}),b},selectChoice:function(a){var b=this.container.find(".select2-search-choice-focus");b.length&&a&&a[0]==b[0]||(b.length&&this.opts.element.trigger("choice-deselected",b),b.removeClass("select2-search-choice-focus"),a&&a.length&&(this.close(),a.addClass("select2-search-choice-focus"),this.opts.element.trigger("choice-selected",a)))},initContainer:function(){var e,d=".select2-choices";this.searchContainer=this.container.find(".select2-search-field"),this.selection=e=this.container.find(d);var f=this;this.selection.on("mousedown",".select2-search-choice",function(){f.search[0].focus(),f.selectChoice(a(this))}),this.search.attr("id","s2id_autogen"+g()),a("label[for='"+this.opts.element.attr("id")+"']").attr("for",this.search.attr("id")),this.search.on("input paste",this.bind(function(){this.isInterfaceEnabled()&&(this.opened()||this.open())})),this.search.attr("tabindex",this.elementTabIndex),this.keydowns=0,this.search.on("keydown",this.bind(function(a){if(this.isInterfaceEnabled()){++this.keydowns;var d=e.find(".select2-search-choice-focus"),f=d.prev(".select2-search-choice:not(.select2-locked)"),g=d.next(".select2-search-choice:not(.select2-locked)"),h=x(this.search);if(d.length&&(a.which==c.LEFT||a.which==c.RIGHT||a.which==c.BACKSPACE||a.which==c.DELETE||a.which==c.ENTER)){var i=d;return a.which==c.LEFT&&f.length?i=f:a.which==c.RIGHT?i=g.length?g:null:a.which===c.BACKSPACE?(this.unselect(d.first()),this.search.width(10),i=f.length?f:g):a.which==c.DELETE?(this.unselect(d.first()),this.search.width(10),i=g.length?g:null):a.which==c.ENTER&&(i=null),this.selectChoice(i),y(a),i&&i.length||this.open(),b}if((a.which===c.BACKSPACE&&1==this.keydowns||a.which==c.LEFT)&&0==h.offset&&!h.length)return this.selectChoice(e.find(".select2-search-choice:not(.select2-locked)").last()),y(a),b;if(this.selectChoice(null),this.opened())switch(a.which){case c.UP:case c.DOWN:return this.moveHighlight(a.which===c.UP?-1:1),y(a),b;case c.ENTER:return this.selectHighlighted(),y(a),b;case c.TAB:return this.selectHighlighted({noFocus:!0}),b;case c.ESC:return this.cancel(a),y(a),b}if(a.which!==c.TAB&&!c.isControl(a)&&!c.isFunctionKey(a)&&a.which!==c.BACKSPACE&&a.which!==c.ESC){if(a.which===c.ENTER){if(this.opts.openOnEnter===!1)return;if(a.altKey||a.ctrlKey||a.shiftKey||a.metaKey)return}this.open(),(a.which===c.PAGE_UP||a.which===c.PAGE_DOWN)&&y(a),a.which===c.ENTER&&y(a)}}})),this.search.on("keyup",this.bind(function(){this.keydowns=0,this.resizeSearch()})),this.search.on("blur",this.bind(function(b){this.container.removeClass("select2-container-active"),this.search.removeClass("select2-focused"),this.selectChoice(null),this.opened()||this.clearSearch(),b.stopImmediatePropagation(),this.opts.element.trigger(a.Event("select2-blur"))})),this.container.on("mousedown",d,this.bind(function(b){this.isInterfaceEnabled()&&(a(b.target).closest(".select2-search-choice").length>0||(this.selectChoice(null),this.clearPlaceholder(),this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.open(),this.focusSearch(),b.preventDefault()))})),this.container.on("focus",d,this.bind(function(){this.isInterfaceEnabled()&&(this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.container.addClass("select2-container-active"),this.dropdown.addClass("select2-drop-active"),this.clearPlaceholder())})),this.initContainerWidth(),this.opts.element.addClass("select2-offscreen"),this.clearSearch()},enableInterface:function(){this.parent.enableInterface.apply(this,arguments)&&this.search.prop("disabled",!this.isInterfaceEnabled())},initSelection:function(){if(""===this.opts.element.val()&&""===this.opts.element.text()&&(this.updateSelection([]),this.close(),this.clearSearch()),this.select||""!==this.opts.element.val()){var c=this;this.opts.initSelection.call(null,this.opts.element,function(a){a!==b&&null!==a&&(c.updateSelection(a),c.close(),c.clearSearch())})}},clearSearch:function(){var a=this.getPlaceholder(),c=this.getMaxSearchWidth();a!==b&&0===this.getVal().length&&this.search.hasClass("select2-focused")===!1?(this.search.val(a).addClass("select2-default"),this.search.width(c>0?c:this.container.css("width"))):this.search.val("").width(10)},clearPlaceholder:function(){this.search.hasClass("select2-default")&&this.search.val("").removeClass("select2-default")},opening:function(){this.clearPlaceholder(),this.resizeSearch(),this.parent.opening.apply(this,arguments),this.focusSearch(),this.updateResults(!0),this.search.focus(),this.opts.element.trigger(a.Event("select2-open"))},close:function(){this.opened()&&this.parent.close.apply(this,arguments)},focus:function(){this.close(),this.search.focus()},isFocused:function(){return this.search.hasClass("select2-focused")},updateSelection:function(b){var c=[],d=[],e=this;a(b).each(function(){0>m(e.id(this),c)&&(c.push(e.id(this)),d.push(this))}),b=d,this.selection.find(".select2-search-choice").remove(),a(b).each(function(){e.addSelectedChoice(this)}),e.postprocessResults()},tokenize:function(){var a=this.search.val();a=this.opts.tokenizer(a,this.data(),this.bind(this.onSelect),this.opts),null!=a&&a!=b&&(this.search.val(a),a.length>0&&this.open())},onSelect:function(a,b){this.triggerSelect(a)&&(this.addSelectedChoice(a),this.opts.element.trigger({type:"selected",val:this.id(a),choice:a}),(this.select||!this.opts.closeOnSelect)&&this.postprocessResults(),this.opts.closeOnSelect?(this.close(),this.search.width(10)):this.countSelectableResults()>0?(this.search.width(10),this.resizeSearch(),this.getMaximumSelectionSize()>0&&this.val().length>=this.getMaximumSelectionSize()&&this.updateResults(!0),this.positionDropdown()):(this.close(),this.search.width(10)),this.triggerChange({added:a}),b&&b.noFocus||this.focusSearch())},cancel:function(){this.close(),this.focusSearch()},addSelectedChoice:function(c){var j,d=!c.locked,e=a("
  • "),f=a("
  • "),g=d?e:f,h=this.id(c),i=this.getVal();j=this.opts.formatSelection(c,g.find("div")),j!=b&&g.find("div").replaceWith("
    "+this.opts.escapeMarkup(j)+"
    "),d&&g.find(".select2-search-choice-close").on("mousedown",y).on("click dblclick",this.bind(function(b){this.isInterfaceEnabled()&&(a(b.target).closest(".select2-search-choice").fadeOut("fast",this.bind(function(){this.unselect(a(b.target)),this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"),this.close(),this.focusSearch()})).dequeue(),y(b))})).on("focus",this.bind(function(){this.isInterfaceEnabled()&&(this.container.addClass("select2-container-active"),this.dropdown.addClass("select2-drop-active"))})),g.data("select2-data",c),g.insertBefore(this.searchContainer),i.push(h),this.setVal(i)},unselect:function(a){var c,d,b=this.getVal();if(a=a.closest(".select2-search-choice"),0===a.length)throw"Invalid argument: "+a+". Must be .select2-search-choice";c=a.data("select2-data"),c&&(d=m(this.id(c),b),d>=0&&(b.splice(d,1),this.setVal(b),this.select&&this.postprocessResults()),a.remove(),this.opts.element.trigger({type:"removed",val:this.id(c),choice:c}),this.triggerChange({removed:c}))},postprocessResults:function(a,b,c){var d=this.getVal(),e=this.results.find(".select2-result"),f=this.results.find(".select2-result-with-children"),g=this;e.each2(function(a,b){var c=g.id(b.data("select2-data"));m(c,d)>=0&&(b.addClass("select2-selected"),b.find(".select2-result-selectable").addClass("select2-selected"))}),f.each2(function(a,b){b.is(".select2-result-selectable")||0!==b.find(".select2-result-selectable:not(.select2-selected)").length||b.addClass("select2-selected")}),-1==this.highlight()&&c!==!1&&g.highlight(0),!this.opts.createSearchChoice&&!e.filter(".select2-result:not(.select2-selected)").length>0&&this.results.append("
  • "+g.opts.formatNoMatches(g.search.val())+"
  • ")},getMaxSearchWidth:function(){return this.selection.width()-q(this.search)},resizeSearch:function(){var a,b,c,d,e,f=q(this.search);a=A(this.search)+10,b=this.search.offset().left,c=this.selection.width(),d=this.selection.offset().left,e=c-(b-d)-f,a>e&&(e=c-f),40>e&&(e=c-f),0>=e&&(e=a),this.search.width(e)},getVal:function(){var a;return this.select?(a=this.select.val(),null===a?[]:a):(a=this.opts.element.val(),p(a,this.opts.separator))},setVal:function(b){var c;this.select?this.select.val(b):(c=[],a(b).each(function(){0>m(this,c)&&c.push(this)}),this.opts.element.val(0===c.length?"":c.join(this.opts.separator)))},buildChangeDetails:function(a,b){for(var b=b.slice(0),a=a.slice(0),c=0;b.length>c;c++)for(var d=0;a.length>d;d++)o(this.opts.id(b[c]),this.opts.id(a[d]))&&(b.splice(c,1),c--,a.splice(d,1),d--);return{added:b,removed:a}},val:function(c,d){var e,f=this;if(0===arguments.length)return this.getVal();if(e=this.data(),e.length||(e=[]),!c&&0!==c)return this.opts.element.val(""),this.updateSelection([]),this.clearSearch(),d&&this.triggerChange({added:this.data(),removed:e}),b;if(this.setVal(c),this.select)this.opts.initSelection(this.select,this.bind(this.updateSelection)),d&&this.triggerChange(this.buildChangeDetails(e,this.data()));else{if(this.opts.initSelection===b)throw Error("val() cannot be called if initSelection() is not defined");this.opts.initSelection(this.opts.element,function(b){var c=a(b).map(f.id);f.setVal(c),f.updateSelection(b),f.clearSearch(),d&&f.triggerChange(this.buildChangeDetails(e,this.data()))})}this.clearSearch()},onSortStart:function(){if(this.select)throw Error("Sorting of elements is not supported when attached to instead.");this.search.width(0),this.searchContainer.hide()},onSortEnd:function(){var b=[],c=this;this.searchContainer.show(),this.searchContainer.appendTo(this.searchContainer.parent()),this.resizeSearch(),this.selection.find(".select2-search-choice").each(function(){b.push(c.opts.id(a(this).data("select2-data")))}),this.setVal(b),this.triggerChange()},data:function(c,d){var f,g,e=this;return 0===arguments.length?this.selection.find(".select2-search-choice").map(function(){return a(this).data("select2-data")}).get():(g=this.data(),c||(c=[]),f=a.map(c,function(a){return e.opts.id(a)}),this.setVal(f),this.updateSelection(c),this.clearSearch(),d&&this.triggerChange(this.buildChangeDetails(g,this.data())),b)}}),a.fn.select2=function(){var d,g,h,i,c=Array.prototype.slice.call(arguments,0),j=["val","destroy","opened","open","close","focus","isFocused","container","onSortStart","onSortEnd","enable","readonly","positionDropdown","data"],k=["val","opened","isFocused","container","data"];return this.each(function(){if(0===c.length||"object"==typeof c[0])d=0===c.length?{}:a.extend({},c[0]),d.element=a(this),"select"===d.element.get(0).tagName.toLowerCase()?i=d.element.prop("multiple"):(i=d.multiple||!1,"tags"in d&&(d.multiple=i=!0)),g=i?new f:new e,g.init(d);else{if("string"!=typeof c[0])throw"Invalid arguments to select2 plugin: "+c;if(0>m(c[0],j))throw"Unknown method: "+c[0];if(h=b,g=a(this).data("select2"),g===b)return;if(h="container"===c[0]?g.container:g[c[0]].apply(g,c.slice(1)),m(c[0],k)>=0)return!1}}),h===b?this:h},a.fn.select2.defaults={width:"copy",loadMorePadding:0,closeOnSelect:!0,openOnEnter:!0,containerCss:{},dropdownCss:{},containerCssClass:"",dropdownCssClass:"",formatResult:function(a,b,c,d){var e=[];return C(a.text,c.term,e,d),e.join("")},formatSelection:function(a){return a?a.text:b},sortResults:function(a){return a},formatResultCssClass:function(){return b},formatNoMatches:function(){return"No matches found"},formatInputTooShort:function(a,b){var c=b-a.length;return"Please enter "+c+" more character"+(1==c?"":"s")},formatInputTooLong:function(a,b){var c=a.length-b;return"Please delete "+c+" character"+(1==c?"":"s")},formatSelectionTooBig:function(a){return"You can only select "+a+" item"+(1==a?"":"s")},formatLoadMore:function(){return"Loading more results..."},formatSearching:function(){return"Searching..."},minimumResultsForSearch:0,minimumInputLength:0,maximumInputLength:null,maximumSelectionSize:0,id:function(a){return a.id},matcher:function(a,b){return(""+b).toUpperCase().indexOf((""+a).toUpperCase())>=0},separator:",",tokenSeparators:[],tokenizer:J,escapeMarkup:function(a){var b={"\\":"\","&":"&","<":"<",">":">",'"':""","'":"'","/":"/"};return(a+"").replace(/[&<>"'\/\\]/g,function(a){return b[a]})},blurOnChange:!1,selectOnBlur:!1,adaptContainerCssClass:function(a){return a},adaptDropdownCssClass:function(){return null}},a.fn.select2.ajaxDefaults={transport:a.ajax,params:{type:"GET",cache:!1,dataType:"json"}},window.Select2={query:{ajax:D,local:E,tags:F},util:{debounce:t,markMatch:C},"class":{"abstract":d,single:e,multi:f}}}}(jQuery); \ No newline at end of file +return b},prepareOpts:function(){var b=this.parent.prepareOpts.apply(this,arguments),c=this;return"select"===b.element.get(0).tagName.toLowerCase()?b.initSelection=function(a,b){var d=[];a.find(":selected").each2(function(a,b){d.push(c.optionToData(b))}),b(d)}:"data"in b&&(b.initSelection=b.initSelection||function(c,d){var e=p(c.val(),b.separator),f=[];b.query({matcher:function(c,d,g){var h=a.grep(e,function(a){return o(a,b.id(g))}).length;return h&&f.push(g),h},callback:a.isFunction(d)?function(){for(var a=[],c=0;e.length>c;c++)for(var g=e[c],h=0;f.length>h;h++){var i=f[h];if(o(g,b.id(i))){a.push(i),f.splice(h,1);break}}d(a)}:a.noop})}),b},selectChoice:function(a){var b=this.container.find(".select2-search-choice-focus");b.length&&a&&a[0]==b[0]||(b.length&&this.opts.element.trigger("choice-deselected",b),b.removeClass("select2-search-choice-focus"),a&&a.length&&(this.close(),a.addClass("select2-search-choice-focus"),this.opts.element.trigger("choice-selected",a)))},initContainer:function(){var e,d=".select2-choices";this.searchContainer=this.container.find(".select2-search-field"),this.selection=e=this.container.find(d);var f=this;this.selection.on("mousedown",".select2-search-choice",function(){f.search[0].focus(),f.selectChoice(a(this))}),this.search.attr("id","s2id_autogen"+g()),a("label[for='"+this.opts.element.attr("id")+"']").attr("for",this.search.attr("id")),this.search.on("input paste",this.bind(function(){this.isInterfaceEnabled()&&(this.opened()||this.open())})),this.search.attr("tabindex",this.elementTabIndex),this.keydowns=0,this.search.on("keydown",this.bind(function(a){if(this.isInterfaceEnabled()){++this.keydowns;var d=e.find(".select2-search-choice-focus"),f=d.prev(".select2-search-choice:not(.select2-locked)"),g=d.next(".select2-search-choice:not(.select2-locked)"),h=x(this.search);if(d.length&&(a.which==c.LEFT||a.which==c.RIGHT||a.which==c.BACKSPACE||a.which==c.DELETE||a.which==c.ENTER)){var i=d;return a.which==c.LEFT&&f.length?i=f:a.which==c.RIGHT?i=g.length?g:null:a.which===c.BACKSPACE?(this.unselect(d.first()),this.search.width(10),i=f.length?f:g):a.which==c.DELETE?(this.unselect(d.first()),this.search.width(10),i=g.length?g:null):a.which==c.ENTER&&(i=null),this.selectChoice(i),y(a),i&&i.length||this.open(),b}if((a.which===c.BACKSPACE&&1==this.keydowns||a.which==c.LEFT)&&0==h.offset&&!h.length)return this.selectChoice(e.find(".select2-search-choice:not(.select2-locked)").last()),y(a),b;if(this.selectChoice(null),this.opened())switch(a.which){case c.UP:case c.DOWN:return this.moveHighlight(a.which===c.UP?-1:1),y(a),b;case c.ENTER:return this.selectHighlighted(),y(a),b;case c.TAB:return this.selectHighlighted({noFocus:!0}),b;case c.ESC:return this.cancel(a),y(a),b}if(a.which!==c.TAB&&!c.isControl(a)&&!c.isFunctionKey(a)&&a.which!==c.BACKSPACE&&a.which!==c.ESC){if(a.which===c.ENTER){if(this.opts.openOnEnter===!1)return;if(a.altKey||a.ctrlKey||a.shiftKey||a.metaKey)return}this.open(),(a.which===c.PAGE_UP||a.which===c.PAGE_DOWN)&&y(a),a.which===c.ENTER&&y(a)}}})),this.search.on("keyup",this.bind(function(){this.keydowns=0,this.resizeSearch()})),this.search.on("blur",this.bind(function(b){this.container.removeClass("select2-container-active"),this.search.removeClass("select2-focused"),this.selectChoice(null),this.opened()||this.clearSearch(),b.stopImmediatePropagation(),this.opts.element.trigger(a.Event("select2-blur"))})),this.container.on("mousedown",d,this.bind(function(b){this.isInterfaceEnabled()&&(a(b.target).closest(".select2-search-choice").length>0||(this.selectChoice(null),this.clearPlaceholder(),this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.open(),this.focusSearch(),b.preventDefault()))})),this.container.on("focus",d,this.bind(function(){this.isInterfaceEnabled()&&(this.container.hasClass("select2-container-active")||this.opts.element.trigger(a.Event("select2-focus")),this.container.addClass("select2-container-active"),this.dropdown.addClass("select2-drop-active"),this.clearPlaceholder())})),this.initContainerWidth(),this.opts.element.addClass("select2-offscreen"),this.clearSearch()},enableInterface:function(){this.parent.enableInterface.apply(this,arguments)&&this.search.prop("disabled",!this.isInterfaceEnabled())},initSelection:function(){if(""===this.opts.element.val()&&""===this.opts.element.text()&&(this.updateSelection([]),this.close(),this.clearSearch()),this.select||""!==this.opts.element.val()){var c=this;this.opts.initSelection.call(null,this.opts.element,function(a){a!==b&&null!==a&&(c.updateSelection(a),c.close(),c.clearSearch())})}},clearSearch:function(){var a=this.getPlaceholder(),c=this.getMaxSearchWidth();a!==b&&0===this.getVal().length&&this.search.hasClass("select2-focused")===!1?(this.search.val(a).addClass("select2-default"),this.search.width(c>0?c:this.container.css("width"))):this.search.val("").width(10)},clearPlaceholder:function(){this.search.hasClass("select2-default")&&this.search.val("").removeClass("select2-default")},opening:function(){this.clearPlaceholder(),this.resizeSearch(),this.parent.opening.apply(this,arguments),this.focusSearch(),this.updateResults(!0),this.search.focus(),this.opts.element.trigger(a.Event("select2-open"))},close:function(){this.opened()&&this.parent.close.apply(this,arguments)},focus:function(){this.close(),this.search.focus()},isFocused:function(){return this.search.hasClass("select2-focused")},updateSelection:function(b){var c=[],d=[],e=this;a(b).each(function(){0>m(e.id(this),c)&&(c.push(e.id(this)),d.push(this))}),b=d,this.selection.find(".select2-search-choice").remove(),a(b).each(function(){e.addSelectedChoice(this)}),e.postprocessResults()},tokenize:function(){var a=this.search.val();a=this.opts.tokenizer(a,this.data(),this.bind(this.onSelect),this.opts),null!=a&&a!=b&&(this.search.val(a),a.length>0&&this.open())},onSelect:function(a,b){this.triggerSelect(a)&&(this.addSelectedChoice(a),this.opts.element.trigger({type:"selected",val:this.id(a),choice:a}),(this.select||!this.opts.closeOnSelect)&&this.postprocessResults(),this.opts.closeOnSelect?(this.close(),this.search.width(10)):this.countSelectableResults()>0?(this.search.width(10),this.resizeSearch(),this.getMaximumSelectionSize()>0&&this.val().length>=this.getMaximumSelectionSize()&&this.updateResults(!0),this.positionDropdown()):(this.close(),this.search.width(10)),this.triggerChange({added:a}),b&&b.noFocus||this.focusSearch())},cancel:function(){this.close(),this.focusSearch()},addSelectedChoice:function(c){var j,d=!c.locked,e=a("
  • "),f=a("
  • "),g=d?e:f,h=this.id(c),i=this.getVal();j=this.opts.formatSelection(c,g.find("div")),j!=b&&g.find("div").replaceWith("
    "+this.opts.escapeMarkup(j)+"
    "),d&&g.find(".select2-search-choice-close").on("mousedown",y).on("click dblclick",this.bind(function(b){this.isInterfaceEnabled()&&(a(b.target).closest(".select2-search-choice").fadeOut("fast",this.bind(function(){this.unselect(a(b.target)),this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"),this.close(),this.focusSearch()})).dequeue(),y(b))})).on("focus",this.bind(function(){this.isInterfaceEnabled()&&(this.container.addClass("select2-container-active"),this.dropdown.addClass("select2-drop-active"))})),g.data("select2-data",c),g.insertBefore(this.searchContainer),i.push(h),this.setVal(i)},unselect:function(a){var c,d,b=this.getVal();if(a=a.closest(".select2-search-choice"),0===a.length)throw"Invalid argument: "+a+". Must be .select2-search-choice";c=a.data("select2-data"),c&&(d=m(this.id(c),b),d>=0&&(b.splice(d,1),this.setVal(b),this.select&&this.postprocessResults()),a.remove(),this.opts.element.trigger({type:"removed",val:this.id(c),choice:c}),this.triggerChange({removed:c}))},postprocessResults:function(a,b,c){var d=this.getVal(),e=this.results.find(".select2-result"),f=this.results.find(".select2-result-with-children"),g=this;e.each2(function(a,b){var c=g.id(b.data("select2-data"));m(c,d)>=0&&(b.addClass("select2-selected"),b.find(".select2-result-selectable").addClass("select2-selected"))}),f.each2(function(a,b){b.is(".select2-result-selectable")||0!==b.find(".select2-result-selectable:not(.select2-selected)").length||b.addClass("select2-selected")}),-1==this.highlight()&&c!==!1&&g.highlight(0),!this.opts.createSearchChoice&&!e.filter(".select2-result:not(.select2-selected)").length>0&&this.results.append("
  • "+g.opts.formatNoMatches(g.search.val())+"
  • ")},getMaxSearchWidth:function(){return this.selection.width()-q(this.search)},resizeSearch:function(){var a,b,c,d,e,f=q(this.search);a=A(this.search)+10,b=this.search.offset().left,c=this.selection.width(),d=this.selection.offset().left,e=c-(b-d)-f,a>e&&(e=c-f),40>e&&(e=c-f),0>=e&&(e=a),this.search.width(e)},getVal:function(){var a;return this.select?(a=this.select.val(),null===a?[]:a):(a=this.opts.element.val(),p(a,this.opts.separator))},setVal:function(b){var c;this.select?this.select.val(b):(c=[],a(b).each(function(){0>m(this,c)&&c.push(this)}),this.opts.element.val(0===c.length?"":c.join(this.opts.separator)))},buildChangeDetails:function(a,b){for(var b=b.slice(0),a=a.slice(0),c=0;b.length>c;c++)for(var d=0;a.length>d;d++)o(this.opts.id(b[c]),this.opts.id(a[d]))&&(b.splice(c,1),c--,a.splice(d,1),d--);return{added:b,removed:a}},val:function(c,d){var e,f=this;if(0===arguments.length)return this.getVal();if(e=this.data(),e.length||(e=[]),!c&&0!==c)return this.opts.element.val(""),this.updateSelection([]),this.clearSearch(),d&&this.triggerChange({added:this.data(),removed:e}),b;if(this.setVal(c),this.select)this.opts.initSelection(this.select,this.bind(this.updateSelection)),d&&this.triggerChange(this.buildChangeDetails(e,this.data()));else{if(this.opts.initSelection===b)throw Error("val() cannot be called if initSelection() is not defined");this.opts.initSelection(this.opts.element,function(b){var c=a(b).map(f.id);f.setVal(c),f.updateSelection(b),f.clearSearch(),d&&f.triggerChange(this.buildChangeDetails(e,this.data()))})}this.clearSearch()},onSortStart:function(){if(this.select)throw Error("Sorting of elements is not supported when attached to instead.");this.search.width(0),this.searchContainer.hide()},onSortEnd:function(){var b=[],c=this;this.searchContainer.show(),this.searchContainer.appendTo(this.searchContainer.parent()),this.resizeSearch(),this.selection.find(".select2-search-choice").each(function(){b.push(c.opts.id(a(this).data("select2-data")))}),this.setVal(b),this.triggerChange()},data:function(c,d){var f,g,e=this;return 0===arguments.length?this.selection.find(".select2-search-choice").map(function(){return a(this).data("select2-data")}).get():(g=this.data(),c||(c=[]),f=a.map(c,function(a){return e.opts.id(a)}),this.setVal(f),this.updateSelection(c),this.clearSearch(),d&&this.triggerChange(this.buildChangeDetails(g,this.data())),b)}}),a.fn.select2=function(){var d,g,h,i,c=Array.prototype.slice.call(arguments,0),j=["val","destroy","opened","open","close","focus","isFocused","container","onSortStart","onSortEnd","enable","readonly","positionDropdown","data"],k=["val","opened","isFocused","container","data"];return this.each(function(){if(0===c.length||"object"==typeof c[0])d=0===c.length?{}:a.extend({},c[0]),d.element=a(this),"select"===d.element.get(0).tagName.toLowerCase()?i=d.element.prop("multiple"):(i=d.multiple||!1,"tags"in d&&(d.multiple=i=!0)),g=i?new f:new e,g.init(d);else{if("string"!=typeof c[0])throw"Invalid arguments to select2 plugin: "+c;if(0>m(c[0],j))throw"Unknown method: "+c[0];if(h=b,g=a(this).data("select2"),g===b)return;if(h="container"===c[0]?g.container:g[c[0]].apply(g,c.slice(1)),m(c[0],k)>=0)return!1}}),h===b?this:h},a.fn.select2.defaults={width:"copy",loadMorePadding:0,closeOnSelect:!0,openOnEnter:!0,containerCss:{},dropdownCss:{},containerCssClass:"",dropdownCssClass:"",formatResult:function(a,b,c,d){var e=[];return C(a.text,c.term,e,d),e.join("")},formatSelection:function(a){return a?a.text:b},sortResults:function(a){return a},formatResultCssClass:function(){return b},formatNoMatches:function(){return"No matches found"},formatInputTooShort:function(a,b){var c=b-a.length;return"Please enter "+c+" more character"+(1==c?"":"s")},formatInputTooLong:function(a,b){var c=a.length-b;return"Please delete "+c+" character"+(1==c?"":"s")},formatSelectionTooBig:function(a){return"You can only select "+a+" item"+(1==a?"":"s")},formatLoadMore:function(){return"Loading more results..."},formatSearching:function(){return"Searching..."},minimumResultsForSearch:0,minimumInputLength:0,maximumInputLength:null,maximumSelectionSize:0,id:function(a){return a.id},matcher:function(a,b){return(""+b).toUpperCase().indexOf((""+a).toUpperCase())>=0},separator:",",tokenSeparators:[],tokenizer:J,escapeMarkup:function(a){var b={"\\":"\","&":"&","<":"<",">":">",'"':""","'":"'","/":"/"};return(a+"").replace(/[&<>"'\/\\]/g,function(a){return b[a]})},blurOnChange:!1,selectOnBlur:!1,adaptContainerCssClass:function(a){return a},adaptDropdownCssClass:function(){return null}},a.fn.select2.ajaxDefaults={transport:a.ajax,params:{type:"GET",cache:!1,dataType:"json"}},window.Select2={query:{ajax:D,local:E,tags:F},util:{debounce:t,markMatch:C},"class":{"abstract":d,single:e,multi:f}}}}(jQuery); From e499ca69276b40d1a9f1bfdf024a337626bf0f33 Mon Sep 17 00:00:00 2001 From: poetwang Date: Sat, 26 Apr 2014 16:39:28 +0800 Subject: [PATCH 09/17] [api2] Fix bug:group msg attachment more than 1 --- seahub/api2/utils.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/seahub/api2/utils.py b/seahub/api2/utils.py index e680f8c8c5..a09a097fb1 100644 --- a/seahub/api2/utils.py +++ b/seahub/api2/utils.py @@ -319,18 +319,21 @@ def group_msg_to_json(msg, get_all_replies): } try: - att = MessageAttachment.objects.get(group_message_id=msg.id) + atts = MessageAttachment.objects.filter(group_message_id=msg.id) except MessageAttachment.DoesNotExist: - att = None + atts = [] - if att: + atts_json = [] + for att in atts: att_json = { 'path' : att.path, 'repo' : att.repo_id, 'type' : att.attach_type, 'src' : att.src, } - ret['att'] = att_json + atts_json.append(att_json) + if len(atts_json) > 0: + ret['atts'] = atts_json reply_list = MessageReply.objects.filter(reply_to=msg) msg.reply_cnt = reply_list.count() From 6737e0395c4952aa3678a8308df819a37e9217f0 Mon Sep 17 00:00:00 2001 From: zhengxie Date: Mon, 28 Apr 2014 13:52:13 +0800 Subject: [PATCH 10/17] [api] refactor group_msg_to_json --- seahub/api2/utils.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/seahub/api2/utils.py b/seahub/api2/utils.py index a09a097fb1..3ff0b8ade8 100644 --- a/seahub/api2/utils.py +++ b/seahub/api2/utils.py @@ -311,25 +311,21 @@ def get_timestamp(msgtimestamp): def group_msg_to_json(msg, get_all_replies): ret = { - 'from_email' : msg.from_email, - 'nickname' : email2nickname(msg.from_email), - 'timestamp' : get_timestamp(msg.timestamp), - 'msg' : msg.message, - 'msgid' : msg.id, + 'from_email': msg.from_email, + 'nickname': email2nickname(msg.from_email), + 'timestamp': get_timestamp(msg.timestamp), + 'msg': msg.message, + 'msgid': msg.id, } - try: - atts = MessageAttachment.objects.filter(group_message_id=msg.id) - except MessageAttachment.DoesNotExist: - atts = [] - atts_json = [] + atts = MessageAttachment.objects.filter(group_message_id=msg.id) for att in atts: att_json = { - 'path' : att.path, - 'repo' : att.repo_id, - 'type' : att.attach_type, - 'src' : att.src, + 'path': att.path, + 'repo': att.repo_id, + 'type': att.attach_type, + 'src': att.src, } atts_json.append(att_json) if len(atts_json) > 0: From d4afde0d3f2ebcaaa1e3ac055233da8df1d638fb Mon Sep 17 00:00:00 2001 From: llj Date: Mon, 28 Apr 2014 14:46:47 +0800 Subject: [PATCH 11/17] [repo] 'details' view: fixed '1 click, 2 requst sent' bug --- seahub/templates/repo.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/seahub/templates/repo.html b/seahub/templates/repo.html index 58af6b4240..2aca8728e0 100644 --- a/seahub/templates/repo.html +++ b/seahub/templates/repo.html @@ -844,13 +844,15 @@ function setPathWidth() { } $(function () { setPathWidth(); }); +{% include "snippets/list_commit_detail.html" %} + function dirOP() { setPathWidth(); $('.path .dir-link').click(dirlinkClick); -$('.lsch').click(function() { +$('.lsch').unbind().click(function() { listCommitDetails($(this).data('url'), $(this).data('time')); return false; }); @@ -1971,7 +1973,6 @@ function updateCmt() { return false; }); {% endif %} -{% include "snippets/list_commit_detail.html" %} {% include "snippets/shared_link_js.html" %} {% include "snippets/bottom_bar.html" %} From 98208f44b74f873b8b665fe706c64531ebbf7426 Mon Sep 17 00:00:00 2001 From: lins05 Date: Mon, 28 Apr 2014 15:19:51 +0800 Subject: [PATCH 12/17] [api login] some android device's secure id is only 15 chars long --- seahub/api2/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seahub/api2/serializers.py b/seahub/api2/serializers.py index 32076c4c6c..61e11dd84a 100644 --- a/seahub/api2/serializers.py +++ b/seahub/api2/serializers.py @@ -90,7 +90,7 @@ class AuthTokenSerializer(serializers.Serializer): elif platform == 'android': # android device id is the 64bit secure id, so it must be 16 chars in hex representation - if len(device_id) != 16: + if len(device_id) != 16 and len(device_id) != 15: raise serializers.ValidationError('invalid device id') elif platform == 'ios': if len(device_id) != 36: From a21c010fcd45c7cd2cf87d13f16dd4340fc2f0a2 Mon Sep 17 00:00:00 2001 From: lins05 Date: Mon, 28 Apr 2014 15:25:54 +0800 Subject: [PATCH 13/17] [api] fixed comment --- seahub/api2/serializers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/seahub/api2/serializers.py b/seahub/api2/serializers.py index 61e11dd84a..1821220a0d 100644 --- a/seahub/api2/serializers.py +++ b/seahub/api2/serializers.py @@ -89,7 +89,9 @@ class AuthTokenSerializer(serializers.Serializer): raise serializers.ValidationError('invalid device id') elif platform == 'android': + # See http://developer.android.com/reference/android/provider/Settings.Secure.html#ANDROID_ID # android device id is the 64bit secure id, so it must be 16 chars in hex representation + # but some user reports their device ids are 15 chars if len(device_id) != 16 and len(device_id) != 15: raise serializers.ValidationError('invalid device id') elif platform == 'ios': From 4d9ba15cfce8540bcc85d5bb1aeec1f8afc8289a Mon Sep 17 00:00:00 2001 From: llj Date: Mon, 28 Apr 2014 17:18:41 +0800 Subject: [PATCH 14/17] [views] moved some functions from views/__init__.py to views/__ajax__.py --- seahub/templates/pubrepo.html | 2 +- seahub/templates/snippets/events_js.html | 2 +- seahub/urls.py | 9 +- seahub/views/__init__.py | 196 +--------------------- seahub/views/ajax.py | 200 ++++++++++++++++++++++- 5 files changed, 204 insertions(+), 205 deletions(-) diff --git a/seahub/templates/pubrepo.html b/seahub/templates/pubrepo.html index bbc3751655..11bfdcacd7 100644 --- a/seahub/templates/pubrepo.html +++ b/seahub/templates/pubrepo.html @@ -69,7 +69,7 @@ $(".cancel-share").click(function() { function repoCreateSuccessCallback() { location.reload(); } -{% url 'seahub.views.public_repo_create' as repo_create_url %} +{% url 'public_repo_create' as repo_create_url %} {% with post_url=repo_create_url %} {% include "snippets/repo_create_js.html" %} {% endwith %} diff --git a/seahub/templates/snippets/events_js.html b/seahub/templates/snippets/events_js.html index 7ca4fbdb4a..bc98cddc9a 100644 --- a/seahub/templates/snippets/events_js.html +++ b/seahub/templates/snippets/events_js.html @@ -4,7 +4,7 @@ function reqEvents(start) { $('#events-loading').removeClass('hide'); $.ajax({ - url:'{{SITE_ROOT}}events/?start=' + start{% if org %} + '&org_id={{ org.org_id }}'{% endif %}, + url:'{% url 'events' %}?start=' + start{% if org %} + '&org_id={{ org.org_id }}'{% endif %}, dataType: 'json', cache: false, success: function(data) { diff --git a/seahub/urls.py b/seahub/urls.py index 502c87d2a2..7666fb53ae 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -57,7 +57,6 @@ urlpatterns = patterns('', # url(r'^home/public/reply/(?P[\d]+)/$', innerpub_msg_reply, name='innerpub_msg_reply'), # url(r'^home/owner/(?P[^/]+)/$', ownerhome, name='ownerhome'), - (r'^repo/create/$', repo_create), (r'^repo/upload_check/$', validate_filename), url(r'^repo/unsetinnerpub/(?P[-0-9a-f]{36})/$', unsetinnerpub, name='unsetinnerpub'), url(r'^repo/set_password/$', repo_set_password, name="repo_set_password"), @@ -74,7 +73,6 @@ urlpatterns = patterns('', url(r'^repo/history/view/(?P[-0-9a-f]{36})/$', repo_history_view, name='repo_history_view'), url(r'^repo/recycle/(?P[-0-9a-f]{36})/$', repo_recycle_view, name='repo_recycle_view'), url(r'^repo/snapshot/view/(?P[-0-9a-f]{36})/$', repo_view_snapshot, name='repo_view_snapshot'), - url(r'^repo/history/changes/(?P[-0-9a-f]{36})/$', repo_history_changes, name='repo_history_changes'), url(r'^repo/(?P[-0-9a-f]{36})/files/$', view_file, name="repo_view_file"), url(r'^repo/(?P[-0-9a-f]{36})/history/files/$', view_history_file, name="view_history_file"), url(r'^repo/(?P[-0-9a-f]{36})/trash/files/$', view_trash_file, name="view_trash_file"), @@ -100,7 +98,6 @@ urlpatterns = patterns('', (r'^file_upload_progress_page/$', file_upload_progress_page), url(r'^activities/$', activities, name='activities'), url(r'^starred/$', starred, name='starred'), - (r'^events/$', events), # ajax (r'^pdf_full_view/$', pdf_full_view), url(r'^i18n/$', i18n, name='i18n'), (r'^download/repo/$', repo_download), @@ -113,6 +110,7 @@ urlpatterns = patterns('', url(r'^download_client_program/$', TemplateView.as_view(template_name="download.html"), name="download_client"), ### Ajax ### + url(r'^ajax/repo/create/$', repo_create, name="repo_create"), (r'^ajax/repo/(?P[-0-9a-f]{36})/remove/$', repo_remove), url(r'^ajax/repo/(?P[-0-9a-f]{36})/dirents/$', get_dirents, name="get_dirents"), @@ -142,6 +140,7 @@ urlpatterns = patterns('', url(r'^ajax/repo/(?P[-0-9a-f]{36})/file/unstar/$', repo_unstar_file, name='repo_unstar_file'), url(r'^ajax/repo/(?P[-0-9a-f]{36})/current_commit/$', get_current_commit, name='get_current_commit'), + url(r'^ajax/repo/(?P[-0-9a-f]{36})/history/changes/$', repo_history_changes, name='repo_history_changes'), url(r'^ajax/repo/(?P[-0-9a-f]{36})/encrypted_file/(?P[0-9a-f]{40})/download/$', download_enc_file, name='download_enc_file'), @@ -157,6 +156,8 @@ urlpatterns = patterns('', url(r'^ajax/space_and_traffic/$', space_and_traffic, name='space_and_traffic'), url(r'^ajax/my-shared-and-group-repos/$', my_shared_and_group_repos, name='my_shared_and_group_repos'), + url(r'^ajax/events/$', events, name="events"), + ### Apps ### (r'^api2/', include('seahub.api2.urls')), (r'^avatar/', include('seahub.avatar.urls')), @@ -213,7 +214,7 @@ if getattr(settings, 'CLOUD_MODE', False): else: urlpatterns += patterns('', url(r'^pubinfo/libraries/$', pubrepo, name='pubrepo'), - (r'^publicrepo/create/$', public_repo_create), + url(r'^ajax/publicrepo/create/$', public_repo_create, name='public_repo_create'), url(r'^pubinfo/groups/$', pubgrp, name='pubgrp'), url(r'^pubinfo/users/$', pubuser, name='pubuser'), ) diff --git a/seahub/views/__init__.py b/seahub/views/__init__.py index 7400c1b766..5ef12252ab 100644 --- a/seahub/views/__init__.py +++ b/seahub/views/__init__.py @@ -72,8 +72,7 @@ from seahub.notifications.models import UserNotification from seahub.options.models import UserOptions, CryptoOptionNotSetError from seahub.profile.models import Profile from seahub.share.models import FileShare, PrivateFileDirShare, UploadLinkShare -from seahub.forms import AddUserForm, RepoCreateForm, \ - RepoPassowrdForm, SharedRepoCreateForm,\ +from seahub.forms import AddUserForm, RepoPassowrdForm, \ SetUserQuotaForm, RepoSettingForm, SharedLinkPasswordForm from seahub.signals import repo_created, repo_deleted from seahub.utils import render_permission_error, render_error, list_to_string, \ @@ -865,53 +864,6 @@ def get_diff(repo_id, arg1, arg2): return lists -@login_required -def repo_history_changes(request, repo_id): - if not request.is_ajax(): - return Http404 - - changes = {} - content_type = 'application/json; charset=utf-8' - - if not access_to_repo(request, repo_id, ''): - return HttpResponse(json.dumps(changes), content_type=content_type) - - repo = get_repo(repo_id) - if not repo: - return HttpResponse(json.dumps(changes), content_type=content_type) - - username = request.user.username - try: - server_crypto = UserOptions.objects.is_server_crypto(username) - except CryptoOptionNotSetError: - # Assume server_crypto is ``False`` if this option is not set. - server_crypto = False - - if repo.encrypted and \ - (repo.enc_version == 1 or (repo.enc_version == 2 and server_crypto)) \ - and not is_passwd_set(repo_id, username): - return HttpResponse(json.dumps(changes), content_type=content_type) - - commit_id = request.GET.get('commit_id', '') - if not commit_id: - return HttpResponse(json.dumps(changes), content_type=content_type) - - changes = get_diff(repo_id, '', commit_id) - - c = get_commit(repo.id, repo.version, commit_id) - if c.parent_id is None: - # A commit is a first commit only if it's parent id is None. - changes['cmt_desc'] = repo.desc - elif c.second_parent_id is None: - # Normal commit only has one parent. - if c.desc.startswith('Changed library'): - changes['cmt_desc'] = _('Changed library name or description') - else: - # A commit is a merge only if it has two parents. - changes['cmt_desc'] = _('No conflict in the merge.') - - return HttpResponse(json.dumps(changes), content_type=content_type) - @login_required def modify_token(request, repo_id): if not validate_owner(request, repo_id): @@ -995,7 +947,7 @@ def myhome(request): owned_repos = seafile_api.get_owned_repo_list(username) calculate_repos_last_modify(owned_repos) - repo_create_url = reverse(repo_create) + repo_create_url = reverse("repo_create") return render_to_response('myhome.html', { "owned_repos": owned_repos, @@ -1151,62 +1103,6 @@ def client_unsync(request): # return HttpResponse(serialized_data, content_type=content_type) # else: # return HttpResponseBadRequest(content_type=content_type) - -@login_required -def public_repo_create(request): - ''' - Handle ajax post to create public repo. - - ''' - if not request.is_ajax() or request.method != 'POST': - return Http404 - - result = {} - content_type = 'application/json; charset=utf-8' - - form = SharedRepoCreateForm(request.POST) - if not form.is_valid(): - result['error'] = str(form.errors.values()[0]) - return HttpResponseBadRequest(json.dumps(result), - content_type=content_type) - - repo_name = form.cleaned_data['repo_name'] - repo_desc = form.cleaned_data['repo_desc'] - permission = form.cleaned_data['permission'] - encryption = int(form.cleaned_data['encryption']) - - uuid = form.cleaned_data['uuid'] - magic_str = form.cleaned_data['magic_str'] - encrypted_file_key = form.cleaned_data['encrypted_file_key'] - - user = request.user.username - - try: - if not encryption: - repo_id = seafile_api.create_repo(repo_name, repo_desc, user, None) - else: - repo_id = seafile_api.create_enc_repo(uuid, repo_name, repo_desc, user, magic_str, encrypted_file_key, enc_version=2) - - # set this repo as inner pub - seafile_api.add_inner_pub_repo(repo_id, permission) - #seafserv_threaded_rpc.set_inner_pub_repo(repo_id, permission) - except SearpcError as e: - repo_id = None - logger.error(e) - - if not repo_id: - result['error'] = _(u'Internal Server Error') - return HttpResponse(json.dumps(result), status=500, - content_type=content_type) - else: - result['success'] = True - repo_created.send(sender=None, - org_id=-1, - creator=user, - repo_id=repo_id, - repo_name=repo_name) - return HttpResponse(json.dumps(result), content_type=content_type) - @login_required def unsetinnerpub(request, repo_id): repo = get_repo(repo_id) @@ -1441,70 +1337,6 @@ def validate_filename(request): content_type = 'application/json; charset=utf-8' return HttpResponse(json.dumps(result), content_type=content_type) -@login_required -def repo_create(request): - ''' - Handle ajax post to create a library. - - ''' - if not request.is_ajax() or request.method != 'POST': - return Http404 - - result = {} - content_type = 'application/json; charset=utf-8' - - form = RepoCreateForm(request.POST) - if not form.is_valid(): - result['error'] = str(form.errors.values()[0]) - return HttpResponseBadRequest(json.dumps(result), - content_type=content_type) - - repo_name = form.cleaned_data['repo_name'] - repo_desc = form.cleaned_data['repo_desc'] - encryption = int(form.cleaned_data['encryption']) - - uuid = form.cleaned_data['uuid'] - magic_str = form.cleaned_data['magic_str'] - encrypted_file_key = form.cleaned_data['encrypted_file_key'] - - username = request.user.username - - try: - if not encryption: - repo_id = seafile_api.create_repo(repo_name, repo_desc, username, - None) - else: - repo_id = seafile_api.create_enc_repo( - uuid, repo_name, repo_desc, username, - magic_str, encrypted_file_key, enc_version=2) - except SearpcError, e: - repo_id = None - - if not repo_id: - result['error'] = _(u"Internal Server Error") - return HttpResponse(json.dumps(result), status=500, - content_type=content_type) - else: - try: - default_lib = (int(request.GET.get('default_lib', 0)) == 1) - except ValueError: - default_lib = False - if default_lib: - UserOptions.objects.set_default_repo(username, repo_id) - - result = { - 'repo_id': repo_id, - 'repo_name': repo_name, - 'repo_desc': repo_desc, - 'repo_enc': encryption, - } - repo_created.send(sender=None, - org_id=-1, - creator=username, - repo_id=repo_id, - repo_name=repo_name) - return HttpResponse(json.dumps(result), content_type=content_type) - def render_file_revisions (request, repo_id): """List all history versions of a file.""" path = request.GET.get('p', '/') @@ -1976,30 +1808,6 @@ def activities(request): 'new_start': start, }, context_instance=RequestContext(request)) -@login_required -def events(request): - if not request.is_ajax(): - raise Http404 - - events_count = 15 - username = request.user.username - start = int(request.GET.get('start')) - - if request.cloud_mode: - org_id = request.GET.get('org_id') - events, start = get_org_user_events(org_id, username, start, events_count) - else: - events, start = get_user_events(username, start, events_count) - events_more = True if len(events) == events_count else False - - event_groups = group_events_data(events) - ctx = {'event_groups': event_groups} - html = render_to_string("snippets/events_body.html", ctx) - - return HttpResponse(json.dumps({'html':html, 'events_more':events_more, - 'new_start': start}), - content_type='application/json; charset=utf-8') - def group_events_data(events): """ Group events according to the date. diff --git a/seahub/views/ajax.py b/seahub/views/ajax.py index f9067cd6a2..59fbd8a340 100644 --- a/seahub/views/ajax.py +++ b/seahub/views/ajax.py @@ -20,21 +20,21 @@ from pysearpc import SearpcError from seahub.auth.decorators import login_required from seahub.contacts.models import Contact -from seahub.forms import RepoNewDirentForm, RepoRenameDirentForm +from seahub.forms import RepoNewDirentForm, RepoRenameDirentForm, \ + RepoCreateForm, SharedRepoCreateForm from seahub.options.models import UserOptions, CryptoOptionNotSetError from seahub.notifications.models import UserNotification -from seahub.signals import upload_file_successful +from seahub.signals import upload_file_successful, repo_created, repo_deleted from seahub.views import get_repo_dirents, validate_owner, \ check_repo_access_permission, get_unencry_rw_repos_by_user, \ - get_system_default_repo_id + get_system_default_repo_id, access_to_repo, get_diff, group_events_data from seahub.views.repo import get_nav_path, get_fileshare, get_dir_share_link, \ get_uploadlink, get_dir_shared_upload_link import seahub.settings as settings -from seahub.signals import repo_deleted from seahub.utils import check_filename_with_rename, EMPTY_SHA1, gen_block_get_url, \ check_and_get_org_by_repo, TRAFFIC_STATS_ENABLED, get_user_traffic_stat,\ new_merge_with_no_conflict, get_commit_before_new_merge, \ - get_repo_last_modify, gen_file_upload_url + get_repo_last_modify, gen_file_upload_url, get_org_user_events, get_user_events from seahub.utils.star import star_file, unstar_file # Get an instance of a logger @@ -1464,3 +1464,193 @@ def get_file_op_url(request, repo_id): url = gen_file_upload_url(token, op_type + '-aj') return HttpResponse(json.dumps({"url": url}), content_type=content_type) + +@login_required +def repo_history_changes(request, repo_id): + if not request.is_ajax(): + return Http404 + + changes = {} + content_type = 'application/json; charset=utf-8' + + if not access_to_repo(request, repo_id, ''): + return HttpResponse(json.dumps(changes), content_type=content_type) + + repo = get_repo(repo_id) + if not repo: + return HttpResponse(json.dumps(changes), content_type=content_type) + + username = request.user.username + try: + server_crypto = UserOptions.objects.is_server_crypto(username) + except CryptoOptionNotSetError: + # Assume server_crypto is ``False`` if this option is not set. + server_crypto = False + + if repo.encrypted and \ + (repo.enc_version == 1 or (repo.enc_version == 2 and server_crypto)) \ + and not is_passwd_set(repo_id, username): + return HttpResponse(json.dumps(changes), content_type=content_type) + + commit_id = request.GET.get('commit_id', '') + if not commit_id: + return HttpResponse(json.dumps(changes), content_type=content_type) + + changes = get_diff(repo_id, '', commit_id) + + c = get_commit(repo.id, repo.version, commit_id) + if c.parent_id is None: + # A commit is a first commit only if it's parent id is None. + changes['cmt_desc'] = repo.desc + elif c.second_parent_id is None: + # Normal commit only has one parent. + if c.desc.startswith('Changed library'): + changes['cmt_desc'] = _('Changed library name or description') + else: + # A commit is a merge only if it has two parents. + changes['cmt_desc'] = _('No conflict in the merge.') + + return HttpResponse(json.dumps(changes), content_type=content_type) + +@login_required +def repo_create(request): + ''' + Handle ajax post to create a library. + + ''' + if not request.is_ajax() or request.method != 'POST': + return Http404 + + result = {} + content_type = 'application/json; charset=utf-8' + + form = RepoCreateForm(request.POST) + if not form.is_valid(): + result['error'] = str(form.errors.values()[0]) + return HttpResponseBadRequest(json.dumps(result), + content_type=content_type) + + repo_name = form.cleaned_data['repo_name'] + repo_desc = form.cleaned_data['repo_desc'] + encryption = int(form.cleaned_data['encryption']) + + uuid = form.cleaned_data['uuid'] + magic_str = form.cleaned_data['magic_str'] + encrypted_file_key = form.cleaned_data['encrypted_file_key'] + + username = request.user.username + + try: + if not encryption: + repo_id = seafile_api.create_repo(repo_name, repo_desc, username, + None) + else: + repo_id = seafile_api.create_enc_repo( + uuid, repo_name, repo_desc, username, + magic_str, encrypted_file_key, enc_version=2) + except SearpcError, e: + repo_id = None + + if not repo_id: + result['error'] = _(u"Internal Server Error") + return HttpResponse(json.dumps(result), status=500, + content_type=content_type) + try: + default_lib = (int(request.GET.get('default_lib', 0)) == 1) + except ValueError: + default_lib = False + if default_lib: + UserOptions.objects.set_default_repo(username, repo_id) + + result = { + 'repo_id': repo_id, + 'repo_name': repo_name, + 'repo_desc': repo_desc, + 'repo_enc': encryption, + } + repo_created.send(sender=None, + org_id=-1, + creator=username, + repo_id=repo_id, + repo_name=repo_name) + return HttpResponse(json.dumps(result), content_type=content_type) + +@login_required +def public_repo_create(request): + ''' + Handle ajax post to create public repo. + + ''' + if not request.is_ajax() or request.method != 'POST': + return Http404 + + result = {} + content_type = 'application/json; charset=utf-8' + + form = SharedRepoCreateForm(request.POST) + if not form.is_valid(): + result['error'] = str(form.errors.values()[0]) + return HttpResponseBadRequest(json.dumps(result), + content_type=content_type) + + repo_name = form.cleaned_data['repo_name'] + repo_desc = form.cleaned_data['repo_desc'] + permission = form.cleaned_data['permission'] + encryption = int(form.cleaned_data['encryption']) + + uuid = form.cleaned_data['uuid'] + magic_str = form.cleaned_data['magic_str'] + encrypted_file_key = form.cleaned_data['encrypted_file_key'] + + user = request.user.username + + try: + if not encryption: + repo_id = seafile_api.create_repo(repo_name, repo_desc, user, None) + else: + repo_id = seafile_api.create_enc_repo(uuid, repo_name, repo_desc, user, magic_str, encrypted_file_key, enc_version=2) + + # set this repo as inner pub + seafile_api.add_inner_pub_repo(repo_id, permission) + #seafserv_threaded_rpc.set_inner_pub_repo(repo_id, permission) + except SearpcError as e: + repo_id = None + logger.error(e) + + if not repo_id: + result['error'] = _(u'Internal Server Error') + return HttpResponse(json.dumps(result), status=500, + content_type=content_type) + + result['success'] = True + repo_created.send(sender=None, + org_id=-1, + creator=user, + repo_id=repo_id, + repo_name=repo_name) + return HttpResponse(json.dumps(result), content_type=content_type) + +@login_required +def events(request): + if not request.is_ajax(): + raise Http404 + + events_count = 15 + username = request.user.username + start = int(request.GET.get('start')) + + if request.cloud_mode: + org_id = request.GET.get('org_id') + events, start = get_org_user_events(org_id, username, start, events_count) + else: + events, start = get_user_events(username, start, events_count) + events_more = True if len(events) == events_count else False + + event_groups = group_events_data(events) + ctx = {'event_groups': event_groups} + html = render_to_string("snippets/events_body.html", ctx) + + return HttpResponse(json.dumps({'html':html, 'events_more':events_more, + 'new_start': start}), + content_type='application/json; charset=utf-8') + From bd3931462ba97889d5c1a2f7938bfbadc3511d89 Mon Sep 17 00:00:00 2001 From: zhengxie Date: Wed, 7 May 2014 16:54:29 +0800 Subject: [PATCH 15/17] Require login when view file --- seahub/views/file.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/seahub/views/file.py b/seahub/views/file.py index 8a699c9102..85fa0f3307 100644 --- a/seahub/views/file.py +++ b/seahub/views/file.py @@ -282,7 +282,8 @@ def file_size_exceeds_preview_limit(file_size, file_type): return True, err else: return False, '' - + +@login_required @repo_passwd_set_required def view_file(request, repo_id): """ @@ -367,7 +368,7 @@ def view_file(request, repo_id): if len(img_list) > 1: img_list.sort(lambda x, y : cmp(x.lower(), y.lower())) - cur_img_index = img_list.index(u_filename) + cur_img_index = img_list.index(u_filename) if cur_img_index != 0: img_prev = posixpath.join(parent_dir, img_list[cur_img_index - 1]) if cur_img_index != len(img_list) - 1: @@ -405,15 +406,15 @@ def view_file(request, repo_id): else: repo_shared_groups = get_shared_groups_by_repo(repo_id) # Filter out groups that user in joined. - groups = [ x for x in repo_shared_groups if is_group_user(x.id, username)] + groups = [x for x in repo_shared_groups if is_group_user(x.id, username)] if len(groups) > 1: ctx = {} ctx['groups'] = groups repogrp_str = render_to_string("snippets/repo_group_list.html", ctx) else: - repogrp_str = '' - - file_path_hash = hashlib.md5(urllib2.quote(path.encode('utf-8'))).hexdigest()[:12] + repogrp_str = '' + + file_path_hash = hashlib.md5(urllib2.quote(path.encode('utf-8'))).hexdigest()[:12] # fetch file contributors and latest contributor contributors, last_modified, last_commit_id = \ @@ -429,7 +430,7 @@ def view_file(request, repo_id): is_starred = is_file_starred(username, repo.id, path.encode('utf-8'), org_id) template = 'view_file_%s.html' % ret_dict['filetype'].lower() - + search_repo_id = None if not repo.encrypted: search_repo_id = repo.id @@ -452,13 +453,13 @@ def view_file(request, repo_id): 'file_content': ret_dict['file_content'], 'file_enc': ret_dict['file_enc'], 'encoding': ret_dict['encoding'], - 'file_encoding_list':ret_dict['file_encoding_list'], + 'file_encoding_list': ret_dict['file_encoding_list'], 'html_exists': ret_dict['html_exists'], 'html_detail': ret_dict.get('html_detail', {}), 'filetype': ret_dict['filetype'], "applet_root": get_ccnetapplet_root(), 'groups': groups, - 'use_pdfjs':USE_PDFJS, + 'use_pdfjs': USE_PDFJS, 'contributors': contributors, 'latest_contributor': latest_contributor, 'last_modified': last_modified, From e2e610b85fb32c2e4ea6ed2fab36fa69a735e719 Mon Sep 17 00:00:00 2001 From: llj Date: Wed, 7 May 2014 17:01:53 +0800 Subject: [PATCH 16/17] [dir/file share] use ajax to get contacts --- seahub/templates/snippets/shared_link_js.html | 22 ++++++++++++++----- seahub/views/file.py | 6 ----- seahub/views/repo.py | 5 ----- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/seahub/templates/snippets/shared_link_js.html b/seahub/templates/snippets/shared_link_js.html index 379c799153..d82dc023fe 100644 --- a/seahub/templates/snippets/shared_link_js.html +++ b/seahub/templates/snippets/shared_link_js.html @@ -1,12 +1,22 @@ {% load i18n %} {% load url from future %} -var share_list = [], contacts = [], contact_email; -{% for contact in contacts %} -contact_email = '{{ contact.contact_email }}'; -share_list.push({value: contact_email, label: contact_email}); -contacts.push({value:contact_email, avatar:'{{contact.avatar|safe}}'}); -{% endfor %} +var share_list = [], contacts = []; +$(function () { + $.ajax({ + url:'{% url 'get_contacts' %}', + cache: false, + dataType: 'json', + success: function(data) { + var contact_list = data['contacts'], contact_email; + 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_urls, type, cur_path) { var path; diff --git a/seahub/views/file.py b/seahub/views/file.py index 8a699c9102..878652d775 100644 --- a/seahub/views/file.py +++ b/seahub/views/file.py @@ -389,11 +389,6 @@ def view_file(request, repo_id): else: file_shared_link = '' - # my contacts used in shared link autocomplete - contacts = Contact.objects.filter(user_email=username) - for c in contacts: - c.avatar = avatar(c.contact_email, 16) - for g in request.user.joined_groups: g.avatar = grp_avatar(g.id, 20) @@ -447,7 +442,6 @@ def view_file(request, repo_id): 'protocol': http_or_https, 'domain': domain, 'file_shared_link': file_shared_link, - 'contacts': contacts, 'err': ret_dict['err'], 'file_content': ret_dict['file_content'], 'file_enc': ret_dict['file_enc'], diff --git a/seahub/views/repo.py b/seahub/views/repo.py index 3045b9718d..a5584b4e30 100644 --- a/seahub/views/repo.py +++ b/seahub/views/repo.py @@ -190,10 +190,6 @@ def render_repo(request, repo): protocol = request.is_secure() and 'https' or 'http' domain = RequestSite(request).domain - contacts = Contact.objects.get_contacts_by_user(username) - for c in contacts: - c.avatar = avatar(c.contact_email, 16) - for g in request.user.joined_groups: g.avatar = grp_avatar(g.id, 20) @@ -259,7 +255,6 @@ def render_repo(request, repo): 'httpserver_root': httpserver_root, 'protocol': protocol, 'domain': domain, - 'contacts': contacts, 'fileshare': fileshare, 'dir_shared_link': dir_shared_link, 'uploadlink': uploadlink, From aaef4ce7818bd723dbd28ae884a56987d50edadc Mon Sep 17 00:00:00 2001 From: zhengxie Date: Wed, 7 May 2014 17:25:22 +0800 Subject: [PATCH 17/17] Fix a few bugs --- seahub/views/ajax.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/seahub/views/ajax.py b/seahub/views/ajax.py index 59fbd8a340..65a42e4c21 100644 --- a/seahub/views/ajax.py +++ b/seahub/views/ajax.py @@ -5,14 +5,14 @@ import logging import simplejson as json from django.core.urlresolvers import reverse -from django.http import HttpResponse, Http404 +from django.http import HttpResponse, Http404, HttpResponseBadRequest from django.template import RequestContext from django.template.loader import render_to_string from django.utils.http import urlquote from django.utils.translation import ugettext as _ import seaserv -from seaserv import seafile_api, seafserv_rpc, \ +from seaserv import seafile_api, seafserv_rpc, is_passwd_set, \ get_related_users_by_repo, get_related_users_by_org_repo, \ is_org_repo_owner, CALC_SHARE_USAGE, seafserv_threaded_rpc, \ get_user_quota_usage, get_user_share_usage @@ -1555,6 +1555,7 @@ def repo_create(request): result['error'] = _(u"Internal Server Error") return HttpResponse(json.dumps(result), status=500, content_type=content_type) + try: default_lib = (int(request.GET.get('default_lib', 0)) == 1) except ValueError: @@ -1650,7 +1651,7 @@ def events(request): ctx = {'event_groups': event_groups} html = render_to_string("snippets/events_body.html", ctx) - return HttpResponse(json.dumps({'html':html, 'events_more':events_more, + return HttpResponse(json.dumps({'html': html, + 'events_more': events_more, 'new_start': start}), - content_type='application/json; charset=utf-8') - + content_type='application/json; charset=utf-8')