mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-01 23:20:51 +00:00
Merge branch 'lj'
Conflicts: organizations/views.py templates/myhome.html templates/snippets/my_owned_repos.html views.py
This commit is contained in:
@@ -149,3 +149,15 @@ def innerpubmsg_reply_added_cb(sender, instance, **kwargs):
|
||||
detail=msg_id)
|
||||
n.save()
|
||||
|
||||
class UserStarredFiles(models.Model):
|
||||
"""Starred files are marked by users to get quick access to it on user
|
||||
home page.
|
||||
|
||||
"""
|
||||
|
||||
email = models.EmailField()
|
||||
org_id = models.IntegerField()
|
||||
repo_id = models.CharField(max_length=36)
|
||||
|
||||
path = models.TextField()
|
||||
is_dir = models.BooleanField()
|
||||
|
@@ -666,7 +666,8 @@ textarea:-moz-placeholder {
|
||||
#repo-create-form {
|
||||
padding:0 20px;
|
||||
}
|
||||
.empty-repo-tips {
|
||||
.empty-repo-tips,
|
||||
.no-starred-file-tips {
|
||||
width: 60%;
|
||||
margin: 25px auto;
|
||||
}
|
||||
@@ -1349,6 +1350,10 @@ textarea:-moz-placeholder {
|
||||
#file-edit-cancel {
|
||||
color:#900;
|
||||
}
|
||||
#star {
|
||||
padding-left:18px;
|
||||
background:#efefef url('../img/star.png') no-repeat scroll 3px 48%;
|
||||
}
|
||||
/* shareadmin */
|
||||
.view-link-alert p {
|
||||
display: inline-block;
|
||||
|
BIN
media/img/star.png
Normal file
BIN
media/img/star.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 476 B |
@@ -32,7 +32,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block right_panel %}
|
||||
<!-- 我拥有的资料库 --><!-- 共享给我的资料库 -->
|
||||
<!-- 我拥有的资料库 共享给我的资料库 星标文件 -->
|
||||
{% include "snippets/my_owned_repos.html" %}
|
||||
|
||||
{% if events %}
|
||||
|
@@ -37,7 +37,7 @@ from seahub.forms import RepoCreateForm, SharedRepoCreateForm
|
||||
import seahub.settings as seahub_settings
|
||||
from seahub.utils import render_error, render_permission_error, gen_token, \
|
||||
validate_group_name, string2list, calculate_repo_last_modify, MAX_INT, \
|
||||
EVENTS_ENABLED, get_org_user_events
|
||||
EVENTS_ENABLED, get_org_user_events, get_starred_files
|
||||
from seahub.views import myhome
|
||||
from seahub.signals import repo_created
|
||||
|
||||
@@ -141,6 +141,7 @@ def org_personal(request, url_prefix):
|
||||
events = None
|
||||
|
||||
quota_usage = seafserv_threaded_rpc.get_org_user_quota_usage(org.org_id, user)
|
||||
starred_files = get_starred_files(user, org_id=org.org_id)
|
||||
|
||||
return render_to_response('organizations/personal.html', {
|
||||
'owned_repos': owned_repos,
|
||||
@@ -154,6 +155,7 @@ def org_personal(request, url_prefix):
|
||||
'nickname': nickname,
|
||||
'events': events,
|
||||
'quota_usage': quota_usage,
|
||||
'starred_files': starred_files,
|
||||
}, context_instance=RequestContext(request))
|
||||
|
||||
@login_required
|
||||
|
@@ -19,30 +19,30 @@
|
||||
|
||||
{% if grpmsg_list or grpmsg_reply_list or orgmsg_list %}
|
||||
<div class="info-item">
|
||||
<h3 class="info-item-top">{% trans "Reminders..." %}</h3>
|
||||
<ul class="info-item-bottom">
|
||||
{% if grpmsg_list %}
|
||||
<li>
|
||||
{% trans "Group" %}
|
||||
{% for grp in grpmsg_list %}
|
||||
<a href="{% url 'group_info' grp.id %}" class="no-bold">{{ grp.group_name }}</a>
|
||||
{% endfor %}
|
||||
{% trans "have new message" %}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if grpmsg_reply_list %}
|
||||
<li><a href="{{ SITE_ROOT }}group/reply/new/" class="no-bold">{{ grpmsg_reply_list|length }}{% trans "group message has new reply" %}</a></li>
|
||||
{% endif %}
|
||||
{% if orgmsg_list %}
|
||||
<li><a href="{% url 'org_msg' %}" class="no-bold">{{ orgmsg_list|length }}{% trans "org messages" %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<h3 class="info-item-top">{% trans "Reminding..." %}</h3>
|
||||
<ul class="info-item-bottom">
|
||||
{% if grpmsg_list %}
|
||||
<li>
|
||||
{% trans "Group" %}
|
||||
{% for grp in grpmsg_list %}
|
||||
<a href="{% url 'group_info' grp.id %}" class="no-bold">{{ grp.group_name }}</a>
|
||||
{% endfor %}
|
||||
{% trans "have new message" %}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if grpmsg_reply_list %}
|
||||
<li><a href="{{ SITE_ROOT }}group/reply/new/" class="no-bold">{{ grpmsg_reply_list|length }}{% trans "group message has new reply" %}</a></li>
|
||||
{% endif %}
|
||||
{% if orgmsg_list %}
|
||||
<li><a href="{% url 'org_msg' %}" class="no-bold">{{ orgmsg_list|length }}{% trans "org messages" %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="info-item">
|
||||
<h3 class="info-item-top">{% trans "Storage Used" %}</h3>
|
||||
<p class="info-item-bottom">{{ quota_usage|filesizeformat }} {% if quota > 0 %}/ {{ quota|filesizeformat }} {% endif %}</p>
|
||||
<h3 class="info-item-top">{% trans "Storage Used" %}</h3>
|
||||
<p class="info-item-bottom">{{ quota_usage|filesizeformat }} {% if quota > 0 %}/ {{ quota|filesizeformat }} {% endif %}</p>
|
||||
</div>
|
||||
|
||||
<!-- 我的群组 -->
|
||||
@@ -52,7 +52,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block right_panel %}
|
||||
<!-- 我拥有的资料库 --><!-- 共享给我的资料库 -->
|
||||
<!-- 我拥有的资料库 共享给我的资料库 星标文件 -->
|
||||
{% include "snippets/my_owned_repos.html" %}
|
||||
|
||||
{% if events %}
|
||||
|
@@ -25,14 +25,12 @@
|
||||
<button class="fright" data="{% url 'file_revisions' repo.id %}?p={{ path|urlencode }}" id="back">返回文件版本列表</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if page_from == 'recycle' %}
|
||||
<div class="w100 ovhd">
|
||||
<h2 class="fleft">
|
||||
{{repo.props.name}} 的文件回收站
|
||||
</h2>
|
||||
<button class="fright" data="{% url 'repo' repo.id %}" id="back">返回同步目录</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if page_from == 'recycle' %}
|
||||
<div class="w100 ovhd">
|
||||
<h2 class="fleft">{{repo.props.name}} 的文件回收站</h2>
|
||||
<button class="fright" data="{% url 'repo' repo.id %}" id="back">返回同步目录</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<div class="w100 ovhd">
|
||||
<p class="path fleft">
|
||||
@@ -65,7 +63,6 @@
|
||||
{{ name }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% endif %} <!-- if page_from == 'recycle' -->
|
||||
</p>
|
||||
|
||||
@@ -75,6 +72,11 @@
|
||||
<button data="{{ SITE_ROOT }}sharedlink/get/?repo_id={{ repo.id }}&p={{ path|urlencode }}&file_name={{ file_name }}" id="get-shared-link">获取分享地址</button>
|
||||
<button id="send-shared-link" class="hide">发送</button>
|
||||
<button data="{{ SITE_ROOT }}sharedlink/remove/?t={{ fileshare.token }}" id="rm-shared-link" class="hide">删除</button>
|
||||
{% if is_starred %}
|
||||
<button id="star" data="starred">取消星标</button>
|
||||
{% else %}
|
||||
<button id="star" data="unstarred">添加星标</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -465,6 +467,48 @@ $('#open-local').click(function () {
|
||||
}
|
||||
}, 2000);
|
||||
});
|
||||
//star
|
||||
$('#star').click(function() {
|
||||
var star_btn = $(this);
|
||||
var state = star_btn.attr('data');
|
||||
$.ajax({
|
||||
url: '{{ SITE_ROOT }}repo/star_file/{{ repo.id }}/',
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
beforeSend: prepareCSRFToken,
|
||||
dataType: 'json',
|
||||
data: {
|
||||
path: '{{ path }}',
|
||||
state: state,
|
||||
org_id: {% if org %} {{ org.org_id }} {% else %} -1 {% endif %}
|
||||
},
|
||||
success:function(data) {
|
||||
if (data['success']) {
|
||||
if (state == 'starred') {
|
||||
feedback('星标取消成功', 'success');
|
||||
star_btn.attr('data', 'unstarred').text('添加星标');
|
||||
} else {
|
||||
feedback('星标添加成功', 'success');
|
||||
star_btn.attr('data', 'starred').text('取消星标');
|
||||
}
|
||||
} else {
|
||||
feedback('操作失败:' + data['err_msg'], 'error');
|
||||
}
|
||||
},
|
||||
error:function(jqXHR, textStatus, errorThrown) {
|
||||
feedback(textStatus + ',操作失败', 'error');
|
||||
}
|
||||
});
|
||||
})
|
||||
.hover(
|
||||
function() {
|
||||
$(this).css('background-color', '#fff');
|
||||
},
|
||||
function() {
|
||||
$(this).css('background-color', '#efefef');
|
||||
}
|
||||
);
|
||||
//'not view_history' ends here
|
||||
{% endif %}
|
||||
|
||||
|
@@ -1,10 +1,12 @@
|
||||
{% load seahub_tags i18n %}
|
||||
{% load url from future %}
|
||||
<h3>{% trans "Libraries" %}</h3>
|
||||
<div id="repos-tabs">
|
||||
<div class="ovhd">
|
||||
<ul class="fleft">
|
||||
<li><a href="#my-own-repos" onfocus="this.blur()">{% trans "I owned" %}</a></li>
|
||||
<li><a href="#repos-shared-to-me" onfocus="this.blur()">{% trans "Share to me" %}</a></li>
|
||||
<li><a href="#repos-shared-to-me" onfocus="this.blur()">{% trans "Shared to me" %}</a></li>
|
||||
<li><a href="#starred-files" onfocus="this.blur()">{% trans "starred" %}</a></li>
|
||||
</ul>
|
||||
<button id="repo-create" class="fright">{% trans "New Library" %}</button>
|
||||
</div>
|
||||
@@ -82,4 +84,28 @@
|
||||
<p class="empty-repo-tips">您的朋友可以将他的资料库共享给您,这些资料库会显示在这里。</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="starred-files">
|
||||
{% if starred_files %}
|
||||
<table>
|
||||
<tr>
|
||||
<th width="5%"></th>
|
||||
<th width="45%">文件名</th>
|
||||
<th width="30%">所属资料库</th>
|
||||
<th width="20%">更新时间</th>
|
||||
</tr>
|
||||
{% for sfile in starred_files %}
|
||||
<tr>
|
||||
<td class="icon-container"><img src="{{ MEDIA_URL }}img/file/{{ sfile.path|file_icon_filter }}" alt="文件" /></td>
|
||||
<td>
|
||||
<a href="{% url 'repo_view_file' sfile.repo.id %}?p={{ sfile.path|urlencode }}">{{ sfile.formatted_path }}</a>
|
||||
</td>
|
||||
<td>{{ sfile.repo.name }}</td>
|
||||
<td>{{ sfile.last_modified|translate_commit_time }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="no-starred-file-tips">您可以把重要的文件加上星标,这样它们就能显示在这里</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
1
urls.py
1
urls.py
@@ -53,6 +53,7 @@ urlpatterns = patterns('',
|
||||
url(r'^repo/revert_dir/(?P<repo_id>[^/]+)/$', repo_revert_dir, name='repo_revert_dir'),
|
||||
url(r'^repo/upload_file/(?P<repo_id>[^/]+)/$', repo_upload_file, name='repo_upload_file'),
|
||||
url(r'^repo/update_file/(?P<repo_id>[^/]+)/$', repo_update_file, name='repo_update_file'),
|
||||
url(r'^repo/star_file/(?P<repo_id>[^/]+)/$', repo_star_file, name='repo_star_file'),
|
||||
(r'^repo/upload_error/(?P<repo_id>[^/]+)/$', upload_file_error),
|
||||
(r'^repo/update_error/(?P<repo_id>[^/]+)/$', update_file_error),
|
||||
url(r'^repo/file_revisions/(?P<repo_id>[^/]+)/$', file_revisions, name='file_revisions'),
|
||||
|
127
utils.py
127
utils.py
@@ -4,12 +4,14 @@ import os
|
||||
import re
|
||||
import random
|
||||
import stat
|
||||
import urllib2
|
||||
|
||||
from django.shortcuts import render_to_response
|
||||
from django.template import RequestContext
|
||||
from django.utils.hashcompat import sha_constructor
|
||||
|
||||
from base.models import FileContributors
|
||||
from base.models import FileContributors, UserStarredFiles
|
||||
from django.utils.hashcompat import md5_constructor
|
||||
|
||||
from pysearpc import SearpcError
|
||||
|
||||
@@ -468,3 +470,126 @@ else:
|
||||
pass
|
||||
def get_org_user_events():
|
||||
pass
|
||||
|
||||
class StarredFile(object):
|
||||
def format_path(self):
|
||||
if self.path == "/":
|
||||
return self.path
|
||||
|
||||
# strip leading slash
|
||||
path = self.path[1:]
|
||||
if path[-1:] == '/':
|
||||
path = path[:-1]
|
||||
return path.replace('/', ' / ')
|
||||
|
||||
def __init__(self, org_id, repo, path, is_dir, last_modified):
|
||||
# always 0 for non-org repo
|
||||
self.org_id = org_id
|
||||
self.repo = repo
|
||||
self.path = path
|
||||
self.formatted_path = self.format_path()
|
||||
self.is_dir = is_dir
|
||||
# always 0 for dir
|
||||
self.last_modified = last_modified
|
||||
|
||||
# org_id > 0: get starred files in org repos
|
||||
# org_id < 0: get starred files in personal repos
|
||||
def get_starred_files(email, org_id=-1):
|
||||
"""Return a list of starred files for some user, sorted descending by the
|
||||
last modified time.
|
||||
|
||||
"""
|
||||
starred_files = UserStarredFiles.objects.filter(email=email, org_id=org_id)
|
||||
|
||||
ret = []
|
||||
for sfile in starred_files:
|
||||
# repo still exists?
|
||||
try:
|
||||
repo = get_repo(sfile.repo_id)
|
||||
except SearpcError:
|
||||
continue
|
||||
|
||||
if not repo:
|
||||
sfile.delete()
|
||||
continue
|
||||
|
||||
# file still exists?
|
||||
file_id = ''
|
||||
if sfile.path != "/":
|
||||
try:
|
||||
file_id = seafserv_threaded_rpc.get_file_by_path(sfile.repo_id, sfile.path)
|
||||
except SearpcError:
|
||||
continue
|
||||
|
||||
if not file_id:
|
||||
sfile.delete()
|
||||
continue
|
||||
|
||||
last_modified = 0
|
||||
if not sfile.is_dir:
|
||||
# last modified
|
||||
path_hash = md5_constructor(urllib2.quote(sfile.path.encode('utf-8'))).hexdigest()[:12]
|
||||
last_modified = get_file_contributors(sfile.repo_id, sfile.path, path_hash, file_id)[1]
|
||||
|
||||
f = StarredFile(sfile.org_id, repo, sfile.path, sfile.is_dir, last_modified)
|
||||
|
||||
ret.append(f)
|
||||
|
||||
# First sort by repo
|
||||
# Within the same repo:
|
||||
# dir > file
|
||||
# dirs are sorted by name ascending
|
||||
# files are sorted by last_modified descending
|
||||
def sort_func(fa, fb):
|
||||
# Different repo?
|
||||
if fa.repo.id != fb.repo.id:
|
||||
ret = cmp(fa.repo.name, fb.repo.name)
|
||||
if ret != 0:
|
||||
return ret
|
||||
else:
|
||||
# two different repo has the same name, compare the id
|
||||
return cmp(fa.repo.id, fb.repo.id)
|
||||
|
||||
# OK, same repo
|
||||
if fa.is_dir and fb.is_dir:
|
||||
return cmp(fa.path, fb.path)
|
||||
elif fa.is_dir and not fb.is_dir:
|
||||
return -1
|
||||
elif fb.is_dir and not fa.is_dir:
|
||||
return 1
|
||||
else:
|
||||
return cmp(fb.last_modified, fa.last_modified)
|
||||
|
||||
ret.sort(sort_func)
|
||||
return ret
|
||||
|
||||
def star_file(email, repo_id, path, is_dir, org_id=-1):
|
||||
if is_file_starred(email, repo_id, path, org_id):
|
||||
return
|
||||
|
||||
f = UserStarredFiles(email=email,
|
||||
org_id=org_id,
|
||||
repo_id=repo_id,
|
||||
path=path,
|
||||
is_dir=is_dir)
|
||||
f.save()
|
||||
|
||||
def unstar_file(email, repo_id, path):
|
||||
try:
|
||||
f = UserStarredFiles.objects.get(email=email,
|
||||
repo_id=repo_id,
|
||||
path=path)
|
||||
except UserStarredFiles.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
f.delete()
|
||||
|
||||
def is_file_starred(email, repo_id, path, org_id=-1):
|
||||
try:
|
||||
f = UserStarredFiles.objects.get(email=email,
|
||||
repo_id=repo_id,
|
||||
path=path,
|
||||
org_id=org_id)
|
||||
return True
|
||||
except UserStarredFiles.DoesNotExist:
|
||||
return False
|
||||
|
42
views.py
42
views.py
@@ -68,7 +68,8 @@ from utils import render_permission_error, render_error, list_to_string, \
|
||||
get_file_revision_id_size, get_ccnet_server_addr_port, \
|
||||
gen_file_get_url, string2list, MAX_INT, \
|
||||
gen_file_upload_url, check_and_get_org_by_repo, \
|
||||
get_file_contributors, EVENTS_ENABLED, get_user_events
|
||||
get_file_contributors, EVENTS_ENABLED, get_user_events, \
|
||||
get_starred_files, star_file, unstar_file, is_file_starred
|
||||
try:
|
||||
from settings import DOCUMENT_CONVERTOR_ROOT
|
||||
if DOCUMENT_CONVERTOR_ROOT[-1:] != '/':
|
||||
@@ -282,6 +283,14 @@ class RepoView(CtxSwitchRequiredMixin, RepoMixin, TemplateResponseMixin,
|
||||
is_group_user(x.id, self.user.username)]
|
||||
return groups
|
||||
|
||||
def is_starred_dir(self):
|
||||
org_id = -1
|
||||
if self.request.user.org:
|
||||
org_id = self.request.user.org['org_id']
|
||||
args = (self.request.user.username, self.repo.id, self.path.encode('utf-8'), org_id)
|
||||
print args
|
||||
return is_file_starred(*args)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['repo'] = self.repo
|
||||
# kwargs['can_access'] = self.can_access
|
||||
@@ -296,6 +305,7 @@ class RepoView(CtxSwitchRequiredMixin, RepoMixin, TemplateResponseMixin,
|
||||
kwargs['accessible_repos'] = self.get_accessible_repos()
|
||||
kwargs['applet_root'] = self.applet_root
|
||||
kwargs['groups'] = self.get_repo_shared_groups()
|
||||
kwargs['is_starred'] = self.is_starred_dir()
|
||||
if len(kwargs['groups']) > 1:
|
||||
ctx = {}
|
||||
ctx['groups'] = kwargs['groups']
|
||||
@@ -919,6 +929,8 @@ def myhome(request):
|
||||
else:
|
||||
events = None
|
||||
|
||||
starred_files = get_starred_files(request.user.username)
|
||||
|
||||
return render_to_response('myhome.html', {
|
||||
"nickname": nickname,
|
||||
"owned_repos": owned_repos,
|
||||
@@ -935,6 +947,7 @@ def myhome(request):
|
||||
"create_shared_repo": False,
|
||||
"allow_public_share": allow_public_share,
|
||||
"events": events,
|
||||
"starred_files": starred_files,
|
||||
}, context_instance=RequestContext(request))
|
||||
|
||||
@login_required
|
||||
@@ -1308,6 +1321,13 @@ def repo_view_file(request, repo_id):
|
||||
else:
|
||||
repogrp_str = ''
|
||||
|
||||
is_starred = False
|
||||
if page_from != 'recycle':
|
||||
org_id = -1
|
||||
if request.user.org:
|
||||
org_id = request.user.org['org_id']
|
||||
is_starred = is_file_starred(request.user.username, repo.id, path.encode('utf-8'), org_id)
|
||||
|
||||
return render_to_response('repo_view_file.html', {
|
||||
'repo': repo,
|
||||
'obj_id': obj_id,
|
||||
@@ -1342,6 +1362,7 @@ def repo_view_file(request, repo_id):
|
||||
'read_only': read_only,
|
||||
'page_from': page_from,
|
||||
'repo_group_str': repogrp_str,
|
||||
'is_starred': is_starred,
|
||||
}, context_instance=RequestContext(request))
|
||||
|
||||
def file_comment(request):
|
||||
@@ -2752,3 +2773,22 @@ def i18n(request):
|
||||
|
||||
return res
|
||||
|
||||
@login_required
|
||||
def repo_star_file(request, repo_id):
|
||||
path = request.POST.get('path')
|
||||
state = request.POST.get('state');
|
||||
|
||||
content_type = 'application/json; charset=utf-8'
|
||||
|
||||
if not (path and state):
|
||||
return HttpResponse(json.dumps({'success':False, 'err_msg':u'参数错误'}),
|
||||
content_type=content_type)
|
||||
|
||||
org_id = int(request.POST.get('org_id'))
|
||||
path = urllib2.unquote(path.encode('utf-8'))
|
||||
is_dir = False
|
||||
if state == 'unstarred':
|
||||
star_file(request.user.username, repo_id, path, is_dir, org_id=org_id)
|
||||
else:
|
||||
unstar_file(request.user.username, repo_id, path)
|
||||
return HttpResponse(json.dumps({'success':True}), content_type=content_type)
|
||||
|
Reference in New Issue
Block a user