From e3f7651c684535ef6cd19ae96ac684a8dbb7bdb7 Mon Sep 17 00:00:00 2001 From: llj Date: Thu, 4 Apr 2013 19:58:00 +0800 Subject: [PATCH] [discuss to grp] use ajax, show discusstions & replies * manually merged some code from zx's branch 'file_discuss' --- base/models.py | 16 +++ group/templates/group/group_discuss.html | 4 +- group/templates/group/group_reply_list.html | 2 +- group/views.py | 135 ++++++++++++------- media/css/seahub.css | 43 +++--- templates/snippets/bottom_bar.html | 91 +++++++++++-- templates/snippets/discussion_list.html | 46 +++++++ templates/snippets/group_recommend_form.html | 5 +- 8 files changed, 259 insertions(+), 83 deletions(-) create mode 100644 templates/snippets/discussion_list.html diff --git a/base/models.py b/base/models.py index 3202fd5480..9d2fc1c005 100644 --- a/base/models.py +++ b/base/models.py @@ -8,6 +8,7 @@ from seaserv import get_emailusers from shortcuts import get_first_object_or_none from base.templatetags.seahub_tags import at_pattern +from group.models import GroupMessage from notifications.models import UserNotification from profile.models import Profile @@ -34,6 +35,21 @@ class FileComment(models.Model): class Meta: ordering = ['-timestamp'] +class FileDiscuss(models.Model): + """ + Model used to represents the relationship between group message and file/dir. + """ + group_message = models.ForeignKey(GroupMessage) + repo_id = models.CharField(max_length=36) + path = models.TextField() + path_hash = models.CharField(max_length=12, db_index=True) + + def save(self, *args, **kwargs): + if not self.path_hash: + from seahub.utils import calc_file_path_hash + self.path_hash = calc_file_path_hash(self.path) + super(FileDiscuss, self).save(*args, **kwargs) + class FileContributors(models.Model): """(repo_id, file path, file_id, contributors)""" diff --git a/group/templates/group/group_discuss.html b/group/templates/group/group_discuss.html index 6004c8cfd2..02a504076a 100644 --- a/group/templates/group/group_discuss.html +++ b/group/templates/group/group_discuss.html @@ -73,7 +73,7 @@ {% for msg in group_msgs.object_list %}
  • {% avatar msg.from_email 48 %} -
    +
    {% if is_staff or msg.from_email == request.user.username %} @@ -127,7 +127,7 @@
  • {% with id=r.from_email|email2id name=r.from_email|email2nickname %} {% avatar r.from_email 28 %} -
    +
    {{ name }} {{ r.timestamp|translate_seahub_time }} {% trans 'Reply' %} diff --git a/group/templates/group/group_reply_list.html b/group/templates/group/group_reply_list.html index b52c10fc8e..e45ef1e7b4 100644 --- a/group/templates/group/group_reply_list.html +++ b/group/templates/group/group_reply_list.html @@ -5,7 +5,7 @@ {% with id=r.from_email|email2id name=r.from_email|email2nickname %}
  • {% avatar r.from_email 28 %} -
    +
    {{ name }} {{ r.timestamp|translate_seahub_time }} {% trans 'Reply' %} diff --git a/group/views.py b/group/views.py index 050c9f424f..2d4cbcff8e 100644 --- a/group/views.py +++ b/group/views.py @@ -15,6 +15,7 @@ from django.shortcuts import render_to_response, redirect from django.template import Context, loader, RequestContext from django.template.loader import render_to_string from django.utils.encoding import smart_str +from django.utils import datetime_safe from django.utils.hashcompat import md5_constructor from django.utils.http import urlquote from django.utils.translation import ugettext as _ @@ -42,6 +43,7 @@ from signals import grpmsg_added, grpmsg_reply_added from settings import GROUP_MEMBERS_DEFAULT_DISPLAY from base.decorators import sys_staff_required from base.mixins import LoginRequiredMixin +from base.models import FileDiscuss from seahub.contacts.models import Contact from seahub.contacts.signals import mail_sended from seahub.notifications.models import UserNotification @@ -55,6 +57,7 @@ from seahub.utils import render_error, render_permission_error, \ from seahub.utils.paginator import Paginator from seahub.utils.slugify import slugify from seahub.utils.file_types import IMAGE +from seahub.utils import calc_file_path_hash from seahub.views import is_registered_user from seahub.forms import RepoCreateForm, SharedRepoCreateForm @@ -748,66 +751,108 @@ def group_unshare_repo(request, repo_id, group_id, from_email): def group_recommend(request): """ Recommend a file or directory to a group. - now changed to 'post a discussion' + now changed to 'Discuss' + for ajax post/get """ - if request.method != 'POST': - raise Http404 + content_type = 'application/json; charset=utf-8' + result = {} + if request.method == 'POST': - next = request.META.get('HTTP_REFERER', None) - if not next: - next = SITE_ROOT - - form = GroupRecommendForm(request.POST) - if form.is_valid(): - repo_id = form.cleaned_data['repo_id'] - attach_type = form.cleaned_data['attach_type'] - path = form.cleaned_data['path'] - message = form.cleaned_data['message'] - groups = request.POST.getlist('groups') # groups is a group_id list, e.g. [u'1', u'7'] - username = request.user.username + form = GroupRecommendForm(request.POST) + if form.is_valid(): + repo_id = form.cleaned_data['repo_id'] + attach_type = form.cleaned_data['attach_type'] + path = form.cleaned_data['path'] + message = form.cleaned_data['message'] + groups = request.POST.getlist('groups') # groups is a group_id list, e.g. [u'1', u'7'] + username = request.user.username - for group_id in groups: - # Check group id format - try: - group_id = int(group_id) - except ValueError: - messages.error(request, _(u'Error: wrong group id')) - return HttpResponseRedirect(next) + groups_not_in = [] + groups_posted_to = [] + for group_id in groups: + # Check group id format + try: + group_id = int(group_id) + except ValueError: + result['err'] = _(u'Error: wrong group id') + return HttpResponse(json.dumps(result), status=400, content_type=content_type) - # Get that group - group = get_group(group_id) - if not group: - continue + group = get_group(group_id) + if not group: + result['err'] = _(u'Error: the group does not exist.') + return HttpResponse(json.dumps(result), status=400, content_type=content_type) - # TODO: Check whether repo is in the group and Im in the group - if not is_group_user(group_id, username): - err_msg = _(u'Error: you are not in group %s.') - messages.error(request, err_msg % group.group_name) - continue + # TODO: Check whether repo is in the group and Im in the group + if not is_group_user(group_id, username): + groups_not_in.append(group.group_name) + continue - # save message to group - gm = GroupMessage(group_id=group_id, from_email=username, + # save message to group + gm = GroupMessage(group_id=group_id, from_email=username, message=message) - gm.save() + gm.save() - # send signal - grpmsg_added.send(sender=GroupMessage, group_id=group_id, + # 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, + # save attachment + ma = MessageAttachment(group_message=gm, repo_id=repo_id, attach_type=attach_type, path=path, src='recommend') - ma.save() + ma.save() - group_url = reverse('group_discuss', args=[group_id]) - msg = _(u'Successfully posted to %(name)s.') %\ - {'url':group_url, 'name':group.group_name} - messages.add_message(request, messages.INFO, msg) + # save discussion + fd = FileDiscuss(group_message=gm, repo_id=repo_id, path=path) + fd.save() + group_url = reverse('group_discuss', args=[group_id]) + groups_posted_to.append(u'%(name)s' % \ + {'url':group_url, 'name':group.group_name}) + + if len(groups_posted_to) > 0: + result['success'] = _(u'Successfully posted to %(groups)s.') % {'groups': ', '.join(groups_posted_to)} + + if len(groups_not_in) > 0: + result['err'] = _(u'Error: you are not in group %s.') % (', '.join(groups_not_in)) + + else: + result['err'] = _(u'Failed') + return HttpResponse(json.dumps(result), status=400, content_type=content_type) + + # request.method == 'GET' else: - messages.add_message(request, messages.ERROR, _(u'Failed.')) - return HttpResponseRedirect(next) + repo_id = request.GET.get('repo_id') + path = request.GET.get('path', None) + repo = get_repo(repo_id) + if not repo: + result['err'] = _(u'Error: the library does not exist.') + return HttpResponse(json.dumps(result), status=400, content_type=content_type) + if path is None: + result['err'] = _(u'Error: no path.') + return HttpResponse(json.dumps(result), status=400, content_type=content_type) + + # get discussions & replies + path_hash = calc_file_path_hash(path) + discussions = FileDiscuss.objects.filter(path_hash=path_hash, repo_id=repo_id) + msg_ids = [ e.group_message_id for e in discussions ] + + grp_msgs = GroupMessage.objects.filter(id__in=msg_ids).order_by('-timestamp') + msg_replies = MessageReply.objects.filter(reply_to__in=grp_msgs) + for msg in grp_msgs: + msg.replies = [] + for reply in msg_replies: + if msg.id == reply.reply_to_id: + msg.replies.append(reply) + msg.reply_cnt = len(msg.replies) + msg.replies = msg.replies[-3:] + + ctx = {} + ctx['messages'] = grp_msgs + html = render_to_string("snippets/discussion_list.html", ctx) + result['html'] = html + return HttpResponse(json.dumps(result), content_type=content_type) + @login_required def create_group_repo(request, group_id): diff --git a/media/css/seahub.css b/media/css/seahub.css index 15a3768145..1bdcc266f4 100644 --- a/media/css/seahub.css +++ b/media/css/seahub.css @@ -1497,7 +1497,7 @@ textarea:-moz-placeholder {/* for FF */ margin:15px 0 20px; } .msg .txt { - width:576px; + margin-left:64px; } .msg a { font-weight: normal; @@ -1545,7 +1545,7 @@ textarea:-moz-placeholder {/* for FF */ margin-left:5px; } .msg-hd .group, -.comment-hd .time { +.discussion-to-grp .time { color: #808080; } .msg-hd .group { @@ -1595,7 +1595,7 @@ textarea:-moz-placeholder {/* for FF */ margin-bottom:15px; } .reply .txt { - width: 93%; + margin-left:38px; } .reply-con { font-size:12px; @@ -1899,10 +1899,8 @@ textarea:-moz-placeholder {/* for FF */ background:#646464; margin-left:5px; } -/* File comment */ -#file-comment, #to-group, -#discuss-to-group-form { +#discuss-to-group { width:400px; padding:0 15px; position:fixed; @@ -1913,27 +1911,19 @@ textarea:-moz-placeholder {/* for FF */ -moz-box-shadow: -1px 1px 1px rgba(0,0,0,.2); -webkit-box-shadow: 0 2px 4px rgba(0,0,0,.2); } -#comment-input { - width:315px; - padding-left:1px; - height:3em; +#discuss-to-group { + padding-bottom:10px; } -.comment { +#discussions-to-grp { + height:auto; + overflow:auto; +} +#discussions-to-grp .msg { width:380px; - padding:15px 0; - border-top:1px solid #e8e8e8; } -.comment .txt { - width:318px; +#discussions-to-grp .reply-input { + width:272px; } -#file-comment-form { - width:380px; - margin:15px 0; -} -.comment-bd { - word-wrap:break-word; -} -#comment-caret, #to-group-caret, #discuss-to-group-caret { width:28px; @@ -1953,8 +1943,11 @@ textarea:-moz-placeholder {/* for FF */ text-decoration:none; } /*discuss to group*/ -#discuss-to-group-form { - padding-bottom:10px; +#discuss-to-group-form .checkbox-label { + display:inline-block; + white-space:nowrap; + padding-right:3px; + margin-right:10px; } #discuss-to-group-form .input { padding:5px; diff --git a/templates/snippets/bottom_bar.html b/templates/snippets/bottom_bar.html index 3ff0674be7..8b2908d331 100644 --- a/templates/snippets/bottom_bar.html +++ b/templates/snippets/bottom_bar.html @@ -6,30 +6,103 @@ $('#main-panel').css('margin-bottom', $('#bottom-bar').outerHeight() + 2); $('#footer').addClass('hide'); {% if groups %} +function getAndHandleDiscussions(data_html) { + $('#discussions-to-grp').html(data_html).css({'max-height':$(window).height() * 0.9 - $('#bottom-bar').height() - $('#discuss-to-group-caret').height() - $('#discuss-to-group-form').outerHeight()}).removeClass('hide'); + {% include 'group/msg_js.html' %} +} $('#discuss').click(function() { - var form = $('#discuss-to-group-form'); + var popup = $('#discuss-to-group'); var caret = $('#discuss-to-group-caret'); - if (form.hasClass('hide')) { - form.removeClass('hide'); + if (popup.hasClass('hide')) { + popup.removeClass('hide'); caret.removeClass('hide'); caret.css({'left': $(this).offset().left}); + $.ajax({ + url:$('#discuss-to-group-form').attr('action'), + dataType: 'json', + contentType: 'application/json; charset=utf-8', + data: { + 'repo_id': '{{ repo.id }}', + 'path': '{{ path }}' + }, + success: function(data) { + getAndHandleDiscussions(data['html']); + }, + error: function(jqXHR, textStatus, errorThrown) { + var err_str = ''; + if (jqXHR.responseText) { + err_str = $.parseJSON(jqXHR.responseText).err; + feedback(err_str, 'error'); + } + } + }); } else { - form.addClass('hide'); + popup.addClass('hide'); caret.addClass('hide'); } }); $(document).click(function(e) { var target = e.target || event.srcElement; - if (!$('#discuss, #discuss-to-group-form, #discuss-to-group-caret').is(target) && !($('#discuss-to-group-form, #discuss-to-group-caret').find('*').is(target))) { - $('#discuss-to-group-form, #discuss-to-group-caret').addClass('hide'); + if (!$('#discuss, #discuss-to-group, #discuss-to-group-caret').is(target) && !($('#discuss-to-group, #discuss-to-group-caret').find('*').is(target))) { + $('#discuss-to-group, #discuss-to-group-caret').addClass('hide'); } }); $('#discuss-submit').click(function() { - if (!$.trim($('#discuss-to-group-form .input').val())) { - apply_form_error('discuss-to-group-form', '{% trans "Please input a discussion." %}'); + var form = $('#discuss-to-group-form'), + form_id = form.attr('id'); + + if (form.find('.checkbox-checked').length == 0) { + apply_form_error(form_id, '{% trans "Please select at least 1 group." %}'); return false; } + + if (!$.trim($('#discuss-to-group-form .input').val())) { + apply_form_error(form_id, '{% trans "Please input a discussion." %}'); + return false; + } + form.find('.error').addClass('hide'); + + var groups = []; + form.find('.checkbox-checked .checkbox-orig').each(function() { + groups.push($(this).val()); + }); + + $.ajax({ + url: form.attr('action'), + type: 'POST', + dataType: 'json', + contentType: 'application/json; charset=utf-8', + beforeSend: prepareCSRFToken, + traditional: true, + data: { + 'groups': groups, + 'message': form.find('.input').val(), + 'repo_id': '{{ repo.id }}', + 'path': '{{ path }}', + 'attach_type': form.find('[name="attach_type"]').val() + }, + success: function(data) { + if(data['success']) { + feedback(data['success'], 'success'); + getAndHandleDiscussions(data['html']); + form.find('.input').val(''); + } + if(data['err']) { + feedback(data['err'], 'error'); + } + }, + error: function(jqXHR, textStatus, errorThrown) { + var err_str = ''; + if (jqXHR.responseText) { + err_str = $.parseJSON(jqXHR.responseText).err; + } else { + err_str = '{% trans "Failed. Please check the network." %}'; + } + apply_form_error(form_id, err_str); + } + }); + return false; }); $('#main').append('
    {{ repo_group_str|escapejs }}
    '); @@ -64,5 +137,5 @@ $(document).click(function(e) { $(function() { var btn_height = $('#bottom-bar button').outerHeight(); $('#discuss-to-group-caret, #to-group-caret, #comment-caret').css({'bottom': btn_height + 1}); - $('#file-comment, #to-group, #discuss-to-group-form').css({'bottom': btn_height + 1 + $('.outer-caret').outerHeight()}); + $('#file-comment, #to-group, #discuss-to-group').css({'bottom': btn_height + 1 + $('.outer-caret').outerHeight()}); }); diff --git a/templates/snippets/discussion_list.html b/templates/snippets/discussion_list.html new file mode 100644 index 0000000000..cd92e4f54f --- /dev/null +++ b/templates/snippets/discussion_list.html @@ -0,0 +1,46 @@ +{% load i18n seahub_tags avatar_tags%} +{% load url from future %} + +{% for msg in messages %} +
  • +{% avatar msg.from_email 48 %} +
    +
    + {{ msg.from_email|email2nickname }} + {{ msg.timestamp|translate_seahub_time }} +

    {{ msg.message|seahub_urlize|find_at|linebreaksbr }}

    + +
    +
    +
    + {% blocktrans with amount=msg.reply_cnt %}{{ amount }} replies{% endblocktrans %} + {% trans "Hide replies" %} +
    + {% if msg.reply_cnt == 0 %} +
      + {% else %} +
        + {% for r in msg.replies %} +
      • + {% with id=r.from_email|email2id name=r.from_email|email2nickname %} + {% avatar r.from_email 28 %} +
        + {{ name }} + {{ r.timestamp|translate_seahub_time }} + {% trans 'Reply' %} +

        {{ r.message|seahub_urlize|find_at }}

        +
        + {% endwith %} +
      • + {% endfor %} +
      + {% endif %} + +

      {% trans "It can not be blank and should be no more than 150 characters." %}

      + + +
      +
      +
    • +{% endfor %} + diff --git a/templates/snippets/group_recommend_form.html b/templates/snippets/group_recommend_form.html index 4850a56e36..ba1cb9bee4 100644 --- a/templates/snippets/group_recommend_form.html +++ b/templates/snippets/group_recommend_form.html @@ -1,6 +1,7 @@ {% load seahub_tags i18n %} {% load url from future %} -
      {% csrf_token %} +
      + {% csrf_token %}

      {% trans "Post a discussion to group" %}

      {% for group in groups %} @@ -31,6 +32,8 @@

      +
        +