mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-01 23:20:51 +00:00
add file contributor list when view file
This commit is contained in:
@@ -24,3 +24,15 @@ class FileComment(models.Model):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['-timestamp']
|
ordering = ['-timestamp']
|
||||||
|
|
||||||
|
class FileContributors(models.Model):
|
||||||
|
"""(repo_id, file path, file_id, contributors)"""
|
||||||
|
|
||||||
|
repo_id = models.CharField(max_length=36, db_index=True)
|
||||||
|
file_id = models.CharField(max_length=40)
|
||||||
|
|
||||||
|
file_path = models.TextField()
|
||||||
|
file_path_hash = models.CharField(max_length=12)
|
||||||
|
|
||||||
|
# email addresses seperated by comma
|
||||||
|
emails = models.TextField()
|
||||||
|
@@ -495,7 +495,7 @@ def group_share_repo(request, repo_id, group_id, from_email):
|
|||||||
return render_error(request, u'共享失败:小组不存在')
|
return render_error(request, u'共享失败:小组不存在')
|
||||||
|
|
||||||
# Check whether user belongs to the group.
|
# Check whether user belongs to the group.
|
||||||
joined = is_group_user(group_id_int, request.user.username)
|
joined = is_group_user(int(group.id), request.user.username)
|
||||||
|
|
||||||
if not joined:
|
if not joined:
|
||||||
return render_error(request, u'共享失败:未加入该小组')
|
return render_error(request, u'共享失败:未加入该小组')
|
||||||
|
14
settings.py
14
settings.py
@@ -167,20 +167,6 @@ AVATAR_CACHE_TIMEOUT = 24 * 60 * 60
|
|||||||
AVATAR_ALLOWED_FILE_EXTS = ('.jpg', '.png', '.jpeg', '.gif')
|
AVATAR_ALLOWED_FILE_EXTS = ('.jpg', '.png', '.jpeg', '.gif')
|
||||||
AUTO_GENERATE_AVATAR_SIZES = (16, 28, 48, 60, 80)
|
AUTO_GENERATE_AVATAR_SIZES = (16, 28, 48, 60, 80)
|
||||||
|
|
||||||
# File upload
|
|
||||||
# FILE_UPLOAD_MAX_MEMORY_SIZE = 0
|
|
||||||
|
|
||||||
# FILE_UPLOAD_TEMP_DIR = "/tmp/seafile-upload"
|
|
||||||
|
|
||||||
# if not os.access(FILE_UPLOAD_TEMP_DIR, os.F_OK):
|
|
||||||
# os.mkdir(FILE_UPLOAD_TEMP_DIR)
|
|
||||||
|
|
||||||
# FILE_UPLOAD_HANDLERS = (
|
|
||||||
# "seahub.utils.UploadProgressCachedHandler",
|
|
||||||
# "django.core.files.uploadhandler.MemoryFileUploadHandler",
|
|
||||||
# "django.core.files.uploadhandler.TemporaryFileUploadHandler",
|
|
||||||
# )
|
|
||||||
|
|
||||||
CACHES = {
|
CACHES = {
|
||||||
'default': {
|
'default': {
|
||||||
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
|
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
|
||||||
|
@@ -41,6 +41,14 @@
|
|||||||
|
|
||||||
<div id="file">
|
<div id="file">
|
||||||
<div id="file-op">
|
<div id="file-op">
|
||||||
|
<div id="file-contributors" style="float:left">
|
||||||
|
<span>{{ contributors|length }} 个贡献者</span>
|
||||||
|
{% for user in contributors %}
|
||||||
|
<span class="file-contributor" data="{{user}}">
|
||||||
|
{{ user|email2nickname}}<a href="{% url 'user_profile' user %}">{% avatar user 24 %}</a>
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
<button id="open-local" data="{{path}}{{dirent.obj_name}}">打开本地文件</button>
|
<button id="open-local" data="{{path}}{{dirent.obj_name}}">打开本地文件</button>
|
||||||
{% if filetype == 'Text' or filetype == 'Image' or filetype == 'SVG' or filetype == 'Markdown' %}
|
{% if filetype == 'Text' or filetype == 'Image' or filetype == 'SVG' or filetype == 'Markdown' %}
|
||||||
<button data="{{ SITE_ROOT }}repo/{{ repo.id }}/{{ obj_id }}/?file_name={{ file_name }}&op=view" id="view-original">原始文件</button>
|
<button data="{{ SITE_ROOT }}repo/{{ repo.id }}/{{ obj_id }}/?file_name={{ file_name }}&op=view" id="view-original">原始文件</button>
|
||||||
|
118
utils.py
118
utils.py
@@ -2,7 +2,6 @@
|
|||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import time
|
|
||||||
import random
|
import random
|
||||||
import stat
|
import stat
|
||||||
|
|
||||||
@@ -10,9 +9,9 @@ from django.shortcuts import render_to_response
|
|||||||
from django.template import RequestContext
|
from django.template import RequestContext
|
||||||
from django.utils.hashcompat import sha_constructor
|
from django.utils.hashcompat import sha_constructor
|
||||||
|
|
||||||
from django.core.files.uploadhandler import FileUploadHandler, StopUpload, \
|
from base.models import FileContributors
|
||||||
StopFutureHandlers
|
|
||||||
from django.core.cache import cache
|
from pysearpc import SearpcError
|
||||||
|
|
||||||
from seaserv import seafserv_rpc, ccnet_threaded_rpc, seafserv_threaded_rpc, \
|
from seaserv import seafserv_rpc, ccnet_threaded_rpc, seafserv_threaded_rpc, \
|
||||||
get_repo, get_commits, get_group_repoids, CCNET_SERVER_ADDR, \
|
get_repo, get_commits, get_group_repoids, CCNET_SERVER_ADDR, \
|
||||||
@@ -141,50 +140,6 @@ def calculate_repo_last_modify(repo_list):
|
|||||||
except:
|
except:
|
||||||
repo.latest_modify = None
|
repo.latest_modify = None
|
||||||
|
|
||||||
class UploadProgressCachedHandler(FileUploadHandler):
|
|
||||||
"""Tracks progress for file uploads. The http post request must contain a
|
|
||||||
header or query parameter, 'X-Progress-ID', which should contain a unique
|
|
||||||
string to identify the upload to be tracked.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, request=None):
|
|
||||||
super(UploadProgressCachedHandler, self).__init__(request)
|
|
||||||
self.progress_id = None
|
|
||||||
self.cache_key = None
|
|
||||||
|
|
||||||
def handle_raw_input(self, input_data, META, content_length, boundary, encoding=None):
|
|
||||||
self.content_length = content_length
|
|
||||||
if 'X-Progress-ID' in self.request.GET :
|
|
||||||
self.progress_id = self.request.GET['X-Progress-ID']
|
|
||||||
elif 'X-Progress-ID' in self.request.META:
|
|
||||||
self.progress_id = self.request.META['X-Progress-ID']
|
|
||||||
# print "handle_raw_input: ", self.progress_id
|
|
||||||
if self.progress_id:
|
|
||||||
self.cache_key = "%s_%s" % (self.request.user.username, self.progress_id )
|
|
||||||
# make cache expiring in 30 seconds
|
|
||||||
cache.set(self.cache_key, {
|
|
||||||
'length': self.content_length,
|
|
||||||
'uploaded' : 0
|
|
||||||
}, 30)
|
|
||||||
|
|
||||||
def new_file(self, field_name, file_name, content_type, content_length, charset=None):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def receive_data_chunk(self, raw_data, start):
|
|
||||||
if self.cache_key:
|
|
||||||
data = cache.get(self.cache_key)
|
|
||||||
data['uploaded'] += self.chunk_size
|
|
||||||
cache.set(self.cache_key, data, 30)
|
|
||||||
return raw_data
|
|
||||||
|
|
||||||
def file_complete(self, file_size):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def upload_complete(self):
|
|
||||||
if self.cache_key:
|
|
||||||
cache.delete(self.cache_key)
|
|
||||||
|
|
||||||
def check_filename_with_rename(repo_id, parent_dir, filename):
|
def check_filename_with_rename(repo_id, parent_dir, filename):
|
||||||
latest_commit = get_commits(repo_id, 0, 1)[0]
|
latest_commit = get_commits(repo_id, 0, 1)[0]
|
||||||
dirents = seafserv_threaded_rpc.list_dir_by_path(latest_commit.id,
|
dirents = seafserv_threaded_rpc.list_dir_by_path(latest_commit.id,
|
||||||
@@ -378,3 +333,70 @@ def check_and_get_org_by_group(group_id, user):
|
|||||||
base_template = 'myhome_base.html'
|
base_template = 'myhome_base.html'
|
||||||
|
|
||||||
return org, base_template
|
return org, base_template
|
||||||
|
|
||||||
|
def get_file_contributors_from_revisions(repo_id, file_path):
|
||||||
|
"""Inspect the file history and get a list of users who have modified the
|
||||||
|
it.
|
||||||
|
|
||||||
|
"""
|
||||||
|
commits = []
|
||||||
|
try:
|
||||||
|
commits = seafserv_threaded_rpc.list_file_revisions(repo_id, file_path, 100)
|
||||||
|
except SearpcError:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Commits are already sorted by date, so the user list is also sorted.
|
||||||
|
users = [ commit.creator_name for commit in commits ]
|
||||||
|
|
||||||
|
# Remove duplicate elements in a list
|
||||||
|
ret = []
|
||||||
|
for user in users:
|
||||||
|
if user not in ret:
|
||||||
|
ret.append(user)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def get_file_contributors(repo_id, file_path, file_path_hash, file_id):
|
||||||
|
"""Get file contributors list from database cache. If not found in cache,
|
||||||
|
try to get it from seaf-server.
|
||||||
|
|
||||||
|
"""
|
||||||
|
contributors = []
|
||||||
|
try:
|
||||||
|
fc = FileContributors.objects.get(repo_id=repo_id,
|
||||||
|
file_path_hash=file_path_hash)
|
||||||
|
except FileContributors.DoesNotExist:
|
||||||
|
# has no cache yet
|
||||||
|
contributors = get_file_contributors_from_revisions (repo_id, file_path)
|
||||||
|
if not contributors:
|
||||||
|
return []
|
||||||
|
emails = ','.join(contributors)
|
||||||
|
file_contributors = FileContributors(repo_id=repo_id,
|
||||||
|
file_id=file_id,
|
||||||
|
file_path=file_path,
|
||||||
|
file_path_hash=file_path_hash,
|
||||||
|
emails=emails)
|
||||||
|
file_contributors.save()
|
||||||
|
else:
|
||||||
|
# cache found
|
||||||
|
if fc.file_id != file_id:
|
||||||
|
# but cache is outdated
|
||||||
|
fc.delete()
|
||||||
|
contributors = get_file_contributors_from_revisions (repo_id, file_path)
|
||||||
|
if not contributors:
|
||||||
|
return []
|
||||||
|
emails = ','.join(contributors)
|
||||||
|
file_contributors = FileContributors(repo_id=repo_id,
|
||||||
|
file_id=file_id,
|
||||||
|
file_path=file_path,
|
||||||
|
file_path_hash=file_path_hash,
|
||||||
|
emails=emails)
|
||||||
|
file_contributors.save()
|
||||||
|
else:
|
||||||
|
# cache is valid
|
||||||
|
if fc.emails:
|
||||||
|
contributors = fc.emails.split(',')
|
||||||
|
else:
|
||||||
|
contributors = []
|
||||||
|
|
||||||
|
return contributors
|
||||||
|
8
views.py
8
views.py
@@ -58,7 +58,8 @@ from utils import render_permission_error, render_error, list_to_string, \
|
|||||||
check_filename_with_rename, get_accessible_repos, EMPTY_SHA1, \
|
check_filename_with_rename, get_accessible_repos, EMPTY_SHA1, \
|
||||||
get_file_revision_id_size, get_ccnet_server_addr_port, \
|
get_file_revision_id_size, get_ccnet_server_addr_port, \
|
||||||
gen_file_get_url, string2list, MAX_INT, \
|
gen_file_get_url, string2list, MAX_INT, \
|
||||||
gen_file_upload_url, check_and_get_org_by_repo
|
gen_file_upload_url, check_and_get_org_by_repo, \
|
||||||
|
get_file_contributors
|
||||||
from seahub.profile.models import Profile
|
from seahub.profile.models import Profile
|
||||||
try:
|
try:
|
||||||
from settings import DOCUMENT_CONVERTOR_ROOT
|
from settings import DOCUMENT_CONVERTOR_ROOT
|
||||||
@@ -968,6 +969,8 @@ def repo_view_file(request, repo_id):
|
|||||||
else:
|
else:
|
||||||
page_next = False
|
page_next = False
|
||||||
comments = comments_plus_one[:per_page]
|
comments = comments_plus_one[:per_page]
|
||||||
|
|
||||||
|
contributors = get_file_contributors(repo_id, path.encode('utf-8'), file_path_hash, obj_id)
|
||||||
|
|
||||||
return render_to_response('repo_view_file.html', {
|
return render_to_response('repo_view_file.html', {
|
||||||
'repo': repo,
|
'repo': repo,
|
||||||
@@ -999,6 +1002,7 @@ def repo_view_file(request, repo_id):
|
|||||||
'page_next': page_next,
|
'page_next': page_next,
|
||||||
'document_swf_exists': document_swf_exists,
|
'document_swf_exists': document_swf_exists,
|
||||||
'DOCUMENT_CONVERTOR_ROOT': DOCUMENT_CONVERTOR_ROOT,
|
'DOCUMENT_CONVERTOR_ROOT': DOCUMENT_CONVERTOR_ROOT,
|
||||||
|
'contributors': contributors,
|
||||||
}, context_instance=RequestContext(request))
|
}, context_instance=RequestContext(request))
|
||||||
|
|
||||||
def repo_file_get(raw_path):
|
def repo_file_get(raw_path):
|
||||||
@@ -1828,7 +1832,7 @@ def render_file_revisions (request, repo_id):
|
|||||||
return render_error(request, error_msg)
|
return render_error(request, error_msg)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
commits = seafserv_threaded_rpc.list_file_revisions(repo_id, path)
|
commits = seafserv_threaded_rpc.list_file_revisions(repo_id, path, 0)
|
||||||
except SearpcError, e:
|
except SearpcError, e:
|
||||||
return render_error(request, e.msg)
|
return render_error(request, e.msg)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user