From 37c0f934bc04281375e0890f5e658111635b9321 Mon Sep 17 00:00:00 2001 From: xiez Date: Sun, 26 Aug 2012 11:48:43 +0800 Subject: [PATCH] Add file comment feature --- base/models.py | 17 ++++++- forms.py | 8 +++ group/models.py | 7 +-- group/templates/group/group_info.html | 9 +++- group/views.py | 5 +- media/css/seahub.css | 4 ++ templates/repo_view_file.html | 50 ++++++++++++++++++- urls.py | 5 +- utils.py | 5 +- views.py | 72 +++++++++++++++++++++++++-- 10 files changed, 165 insertions(+), 17 deletions(-) diff --git a/base/models.py b/base/models.py index 5eac114b0c..ad1f2236fd 100644 --- a/base/models.py +++ b/base/models.py @@ -1,3 +1,4 @@ +import datetime from django.db import models @@ -8,4 +9,18 @@ class UuidObjidMap(models.Model): uuid = models.CharField(max_length=40) obj_id = models.CharField(max_length=40, unique=True) - +class FileComment(models.Model): + """ + Model used for leave comment on file. + NOTE: + Need manually create index for (file_path_hash, repo_id). + """ + repo_id = models.CharField(max_length=36, db_index=True) + file_path = models.TextField() + file_path_hash = models.CharField(max_length=12) + from_email = models.EmailField() + message = models.TextField() + timestamp = models.DateTimeField(default=datetime.datetime.now) + + class Meta: + ordering = ['-timestamp'] diff --git a/forms.py b/forms.py index 5a4141c27d..04f2024a7f 100644 --- a/forms.py +++ b/forms.py @@ -47,6 +47,14 @@ class FileLinkShareForm(forms.Form): email = forms.CharField(max_length=512) file_shared_link = forms.CharField(max_length=40) +class FileCommentForm(forms.Form): + """ + Form for leave comments on file. + """ + repo_id = forms.CharField(max_length=36) + file_path = forms.CharField() + message = forms.CharField() + class RepoCreateForm(forms.Form): """ Form for creating repo and org repo. diff --git a/group/models.py b/group/models.py index 45950daa2d..f8a033346e 100644 --- a/group/models.py +++ b/group/models.py @@ -25,13 +25,14 @@ class MessageReply(models.Model): class MessageAttachment(models.Model): """ - A model used to represents a message attachment related to a group message. + Model used to represents a message attachment related to a group message. """ group_message = models.ForeignKey(GroupMessage) repo_id = models.CharField(max_length=40) - attach_type = models.CharField(max_length=5) + attach_type = models.CharField(max_length=5) # `file` or `dir` path = models.TextField() - + src = models.TextField(max_length=20) # `recommend` or `filecomment` + at_pattern = re.compile(r'(\s|^)(@\w+)', flags=re.U) @receiver(post_save, sender=MessageReply) diff --git a/group/templates/group/group_info.html b/group/templates/group/group_info.html index a35cd0b5f9..0eff662be7 100644 --- a/group/templates/group/group_info.html +++ b/group/templates/group/group_info.html @@ -107,7 +107,8 @@

- {% if msg.attachment %} + {% if msg.attachment.src == 'recommend' %} + 推荐 {% if msg.attachment.attach_type == 'file' %} @@ -117,6 +118,12 @@ {{ msg.attachment.name }} : {% endif %} + + {% if msg.attachment.src == 'filecomment' %} + + 文件 {{ msg.attachment.name }} 有新的评注: + {% endif %} + {{ msg.message|seahub_urlize|find_at|linebreaksbr }}

diff --git a/group/views.py b/group/views.py index a2eb2b3e3f..ec5f0c74c6 100644 --- a/group/views.py +++ b/group/views.py @@ -207,7 +207,7 @@ def render_group_info(request, group_id, form): group_id=group_id).order_by(\ '-timestamp')[per_page*(current_page-1) : per_page*current_page+1] - if len(msgs_plus_one) == per_page + 1: + if msgs_plus_one.count() == per_page + 1: page_next = True else: page_next = False @@ -582,7 +582,8 @@ def group_recommend(request): # save attachment ma = MessageAttachment(group_message=gm, repo_id=repo_id, - attach_type=attach_type, path=path) + attach_type=attach_type, path=path, + src='recommend') ma.save() group_url = reverse('group_info', args=[group.id]) diff --git a/media/css/seahub.css b/media/css/seahub.css index 0182273a3a..687b7b5c6a 100644 --- a/media/css/seahub.css +++ b/media/css/seahub.css @@ -1023,3 +1023,7 @@ ul.with-bg li { /* add padding to account for vertical scrollbar */ padding-right: 20px; } + +#shared-link { + display: inline-block; +} diff --git a/templates/repo_view_file.html b/templates/repo_view_file.html index 4c63c6f2ab..e6d4e15438 100644 --- a/templates/repo_view_file.html +++ b/templates/repo_view_file.html @@ -1,5 +1,5 @@ {% extends base_template %} -{% load seahub_tags %} +{% load seahub_tags avatar_tags%} {% load url from future %} {% block main_panel %} @@ -37,7 +37,7 @@
{% if not view_history %} - {{ file_shared_link }} + @@ -89,6 +89,52 @@
+{% if request.user.is_authenticated %} +
+
+ + +
+ {% for error in form.message.errors %} +

{{ error|escape }}

+ {% endfor %} + +
+
+{% endif %} + +{% if comments %} + + +{% endif %} +
+ {% if current_page != 1 %} + 上一页 + {% endif %} + {% if page_next %} + 下一页 + {% endif %} +
+ {% endblock %} {% block extra_script %} diff --git a/urls.py b/urls.py index 7d4dcde689..214bd1e569 100644 --- a/urls.py +++ b/urls.py @@ -11,7 +11,8 @@ from seahub.views import root, myhome, \ seafile_access_check, repo_history_changes, \ repo_upload_file, file_upload_progress_page, \ upload_file_error, update_file_error, \ - get_subdir, file_move, repo_new_dir, repo_new_file, repo_rename_file, validate_filename, \ + get_subdir, file_move, repo_new_dir, repo_new_file, repo_rename_file, \ + validate_filename, \ repo_create, repo_update_file, repo_revert_file, file_revisions, \ get_shared_link, view_shared_file, remove_shared_link, send_shared_link, \ crocodoc_upload, crocodoc_status, crocodoc_session @@ -77,7 +78,7 @@ urlpatterns = patterns('', (r'^download/repo/$', repo_download), (r'^file/move/get_subdir/$', get_subdir), - (r'^file/move/$', file_move), + (r'^file/move/$', file_move), (r'^seafile_access_check/$', seafile_access_check), url(r'^org/remove/(?P[\d]+)/$', org_remove, name="org_remove"), (r'^org/$', org_info), diff --git a/utils.py b/utils.py index 17f3b893cb..0e9e3b276c 100644 --- a/utils.py +++ b/utils.py @@ -3,6 +3,7 @@ import os import re import time +import random import stat from django.shortcuts import render_to_response @@ -101,7 +102,9 @@ def gen_token(max_length=5): """ - token = sha_constructor(settings.SECRET_KEY + unicode(time.time())).hexdigest()[:max_length] + secret_key = settings.SECRET_KEY + rstr = str(random.random()) + token = sha_constructor(secret_key + rstr).hexdigest()[:max_length] return token def validate_group_name(group_name): diff --git a/views.py b/views.py index 3101096feb..8f169181d0 100644 --- a/views.py +++ b/views.py @@ -20,6 +20,7 @@ from django.http import HttpResponse, HttpResponseBadRequest, Http404, \ HttpResponseRedirect from django.shortcuts import render_to_response, redirect from django.template import Context, loader, RequestContext +from django.utils.hashcompat import md5_constructor from django.views.decorators.csrf import csrf_protect from auth.decorators import login_required @@ -36,13 +37,15 @@ from pysearpc import SearpcError from base.accounts import User from base.decorators import sys_staff_required -from seahub.base.models import UuidObjidMap +from seahub.base.models import UuidObjidMap, FileComment from seahub.contacts.models import Contact from seahub.contacts.signals import mail_sended +from group.models import GroupMessage, MessageAttachment +from group.signals import grpmsg_added from seahub.notifications.models import UserNotification from seahub.organizations.utils import access_org_repo from forms import AddUserForm, FileLinkShareForm, RepoCreateForm, \ - RepoNewDirForm, RepoNewFileForm + RepoNewDirForm, RepoNewFileForm, FileCommentForm from utils import render_permission_error, render_error, list_to_string, \ get_httpserver_root, get_ccnetapplet_root, gen_token, \ calculate_repo_last_modify, valid_previewed_file, \ @@ -724,10 +727,44 @@ def repo_view_file(request, repo_id): """ Preview file on web, including files in current worktree and history. """ + if request.method == 'POST': + # handle post request to leave comment on file + path = request.GET.get('p', '/') + next = reverse('repo_view_file', args=[repo_id]) + '?p=' + \ + urllib2.quote(path.encode('utf-8')) + + f = FileCommentForm(request.POST) + if f.is_valid(): + repo_id = f.cleaned_data['repo_id'] + file_path = f.cleaned_data['file_path'] + file_path_hash = md5_constructor(file_path).hexdigest()[:12] + message = f.cleaned_data['message'] + fc = FileComment(repo_id=repo_id, file_path=file_path, + file_path_hash=file_path_hash, + from_email=request.user.username, message=message) + fc.save() + # send a group message if the repo shared to any groups + repo_shared_groups = get_shared_groups_by_repo(repo_id) + + for group in repo_shared_groups: + # save group message, and length should be less than 500 + gm = GroupMessage(group_id=group.id, + from_email=request.user.username, + message=message[:500]) + gm.save() + # send signal + grpmsg_added.send(sender=GroupMessage, group_id=group.id, + from_email=request.user.username) + + # save attachment + ma = MessageAttachment(group_message=gm, repo_id=repo_id, + attach_type='file', path=path, + src='filecomment') + ma.save() + return HttpResponseRedirect(next) + http_server_root = get_httpserver_root() path = request.GET.get('p', '/') - if path[-1] == '/': - path = path[:-1] u_filename = os.path.basename(path) filename = urllib2.quote(u_filename.encode('utf-8')) @@ -800,6 +837,25 @@ def repo_view_file(request, repo_id): # check whether user joined this group if is_group_user(group.id, request.user.username): groups.append(group) + + """file comments""" + # Make sure page request is an int. If not, deliver first page. + try: + current_page = int(request.GET.get('page', '1')) + per_page= int(request.GET.get('per_page', '15')) + except ValueError: + current_page = 1 + per_page = 15 + + file_path_hash = md5_constructor(urllib2.quote(path.encode('utf-8'))).hexdigest()[:12] + comments_plus_one = FileComment.objects.filter( + file_path_hash=file_path_hash, + repo_id=repo_id)[per_page*(current_page-1) : per_page*current_page+1] + if comments_plus_one.count() == per_page + 1: + page_next = True + else: + page_next = False + comments = comments_plus_one[:per_page] return render_to_response('repo_view_file.html', { 'repo': repo, @@ -823,8 +879,14 @@ def repo_view_file(request, repo_id): 'file_content': file_content, "applet_root": get_ccnetapplet_root(), 'groups': groups, + 'comments': comments, + 'current_page': current_page, + 'prev_page': current_page-1, + 'next_page': current_page+1, + 'per_page': per_page, + 'page_next': page_next, }, context_instance=RequestContext(request)) - + def repo_file_get(raw_path): err = '' file_content = ''