diff options
-rw-r--r-- | askbot/skins/common/media/js/group_messaging.js | 186 | ||||
-rw-r--r-- | askbot/skins/default/media/style/style.less | 2 | ||||
-rw-r--r-- | askbot/skins/default/templates/group_messaging/home.html | 12 | ||||
-rw-r--r-- | askbot/skins/default/templates/group_messaging/senders_list.html | 9 | ||||
-rw-r--r-- | askbot/skins/default/templates/group_messaging/threads_list.html | 13 | ||||
-rw-r--r-- | askbot/skins/default/templates/user_inbox/base.html | 6 | ||||
-rw-r--r-- | askbot/skins/default/templates/user_inbox/messages.html | 42 | ||||
-rw-r--r-- | askbot/urls.py | 1 | ||||
-rw-r--r-- | askbot/views/users.py | 22 | ||||
-rw-r--r-- | group_messaging/urls.py | 22 | ||||
-rw-r--r-- | group_messaging/views.py | 135 |
11 files changed, 380 insertions, 70 deletions
diff --git a/askbot/skins/common/media/js/group_messaging.js b/askbot/skins/common/media/js/group_messaging.js new file mode 100644 index 00000000..3d64d660 --- /dev/null +++ b/askbot/skins/common/media/js/group_messaging.js @@ -0,0 +1,186 @@ +var HideableWidget = function() { + Widget.call(this); +}; +inherits(HideableWidget, Widget); + +HideableWidget.prototype.setState = function(state) { + this._state = state; + if (this._element) { + if (state === 'shown') { + this._element.show(); + } else if (state === 'hidden') { + this._element.hide(); + } + } +}; + +HideableWidget.prototype.show = function() { + this.setState('shown'); +}; + +HideableWidget.prototype.hide = function() { + this.setState('hidden'); +}; + +/** + * @constructor + */ +var MessageComposer = function() { + HideableWidget.call(this); +}; +inherits(MessageComposer, HideableWidget); + +MessageComposer.prototype.send = function() { +}; + +MessageComposer.prototype.onAfterCancel = function(handler) { + if (handler) { + this._onAfterCancel = handler; + } else { + return this._onAfterCancel(); + } +}; + +MessageComposer.prototype.onAfterSend = function(handler) { + if (handler) { + this._onAfterSend = handler; + } else { + return this._onAfterSend(); + } +}; + +MessageComposer.prototype.cancel = function() { + this._textarea.val(''); + this.hide(); + this.onAfterCancel(); +}; + +MessageComposer.prototype.setPostData = function(data) { + this._postData = data; +}; + +MessageComposer.prototype.getPostData = function() { + return this._postData; +}; + +MessageComposer.prototype.setSendUrl = function(url) { + this._sendUrl = url; +}; + +MessageComposer.prototype.send = function() { + var url = this._sendUrl; + var data = this.getPostData(); + var me = this; + data['text'] = this._textarea.val(); + $.ajax({ + type: 'POST', + dataType: 'json', + url: url, + data: data, + cache: false, + success: function() { me.onAfterSend(); } + }); +}; + +MessageComposer.prototype.createDom = function() { + this._element = this.makeElement('div'); + this.hide(); + this._element.addClass('message-composer'); + //create textarea + var textarea = this.makeElement('textarea'); + this._element.append(textarea); + this._element.append(this.makeElement('br')); + this._textarea = textarea; + //send button + var me = this; + var sendBtn = this.makeButton( + gettext('send'), + function() { me.send(); } + ); + sendBtn.addClass('submit'); + this._element.append(sendBtn); + //cancel button + var cancelBtn = this.makeButton( + gettext('cancel'), + function() { me.cancel(); } + ); + cancelBtn.addClass('submit'); + this._element.append(cancelBtn); +}; + + +/** + * @constructor + */ +var ThreadsList = function() { + HideableWidget.call(this); +}; +inherits(ThreadsList, HideableWidget); + + +/** + * @constructor + */ +var Message = function() { + Widget.call(this); +}; +inherits(Message, Widget); + + +/** + * @constructor + */ +var Thread = function() { + HideableWidget.call(this); +}; +inherits(Thread, HideableWidget); + + +/** + * @contsructor + */ +var MessageCenter = function() { + Widget.call(this); +}; +inherits(MessageCenter, Widget); + +MessageCenter.prototype.setState = function(state) { + this._editor.hide(); + this._threadsList.hide(); + //this._thread.hide(); + if (state === 'compose') { + this._editor.show(); + } else if (state === 'show-list') { + this._threadsList.show(); + } else if (state === 'show-thread') { + this._thread.show(); + } +}; + +MessageCenter.prototype.decorate = function(element) { + this._element = element; + this._firstCol = element.find('.first-col'); + this._secondCol = element.find('.second-col'); + //read sender list + //read message list + + var me = this; + //create editor + var editor = new MessageComposer(); + this._secondCol.append(editor.getElement()); + editor.setSendUrl(element.data('createThreadUrl')); + editor.onAfterCancel(function() { me.setState('show-list') }); + editor.onAfterSend(function() { + me.setState('show-list'); + notify.show(gettext('message sent'), true); + }); + this._editor = editor; + + //activate compose button + var btn = element.find('button.compose'); + this._composeBtn = btn; + setupButtonEventHandlers(btn, function(){ me.setState('compose') }); +}; + +var msgCtr = new MessageCenter(); +msgCtr.decorate($('.group-messaging')); diff --git a/askbot/skins/default/media/style/style.less b/askbot/skins/default/media/style/style.less index d898852e..eb30d78e 100644 --- a/askbot/skins/default/media/style/style.less +++ b/askbot/skins/default/media/style/style.less @@ -2686,7 +2686,7 @@ a:hover.medal { .tabBar-profile{ width:100%; - margin-bottom:15px; + margin-bottom:5px; float:left; } diff --git a/askbot/skins/default/templates/group_messaging/home.html b/askbot/skins/default/templates/group_messaging/home.html new file mode 100644 index 00000000..b6733624 --- /dev/null +++ b/askbot/skins/default/templates/group_messaging/home.html @@ -0,0 +1,12 @@ +<div class="group-messaging" + data-create-thread-url="{% url create_thread %}" +> + <div class="first-col"> + <button class="submit compose">{% trans %}compose{% endtrans %}</button> + {% include "group_messaging/senders_list.html" %} + </div> + <div class="second-col"> + {% include "group_messaging/threads_list.html" %} + </div> + <div class="clear-fix"></div> +</div> diff --git a/askbot/skins/default/templates/group_messaging/senders_list.html b/askbot/skins/default/templates/group_messaging/senders_list.html new file mode 100644 index 00000000..0b6de42f --- /dev/null +++ b/askbot/skins/default/templates/group_messaging/senders_list.html @@ -0,0 +1,9 @@ +{% if senders %} +<ul id="senders-list"> +{% for sender in senders %} + <li>{% trans %}Senders:{% endtrans %}</li> + <li><a data-sender-id="-1">{% trans %}all{% endtrans %}</a></li> + <li><a data-sender-id="{{ sender.id }}">{{ sender.username|escape }}</a></li> +{% endfor %} +</ul> +{% endif %} diff --git a/askbot/skins/default/templates/group_messaging/threads_list.html b/askbot/skins/default/templates/group_messaging/threads_list.html new file mode 100644 index 00000000..c9376f56 --- /dev/null +++ b/askbot/skins/default/templates/group_messaging/threads_list.html @@ -0,0 +1,13 @@ +<ul id="threads-list"> +{% if threads %} + {% for thread in threads %} + <li> + <a data-thread-id="{{ thread.id }}"> + {{ thread.headline|escape }} + </a> + </li> + {% endfor %} +{% else %} + <li class="empty">{% trans %}there are no messages yet...{% endtrans %}</li> +{% endif %} +</ul> diff --git a/askbot/skins/default/templates/user_inbox/base.html b/askbot/skins/default/templates/user_inbox/base.html index 4d74f8bc..890cb0f7 100644 --- a/askbot/skins/default/templates/user_inbox/base.html +++ b/askbot/skins/default/templates/user_inbox/base.html @@ -10,10 +10,13 @@ {% set re_count = request.user.new_response_count + request.user.seen_response_count %} - {% if re_count + flags_count + group_join_requests_count > 0 %} <div id="re_sections"> {% trans %}Sections:{% endtrans %} {% set sep = joiner('|') %} + {{ sep() }} + <a href="{{request.user.get_absolute_url()}}?sort=inbox§ion=messages" + {% if inbox_section == 'messages' %}class="on"{% endif %} + >{% trans %}messages{% endtrans %}</a> {% if re_count > 0 %}{{ sep() }} <a href="{{request.user.get_absolute_url()}}?sort=inbox§ion=forum" {% if inbox_section == 'forum' %}class="on"{% endif %} @@ -36,7 +39,6 @@ </a> {% endif %} </div> - {% endif %} {% block inbox_content %} {% endblock %} </div> diff --git a/askbot/skins/default/templates/user_inbox/messages.html b/askbot/skins/default/templates/user_inbox/messages.html new file mode 100644 index 00000000..7623a482 --- /dev/null +++ b/askbot/skins/default/templates/user_inbox/messages.html @@ -0,0 +1,42 @@ +{% extends "user_inbox/base.html" %} +{% import "macros.html" as macros %} +{% block before_css %} + {{ super() }} + <style type="text/css"> + .group-messaging { + padding-top: 5px; + } + .group-messaging li { + list-style-type: none; + list-style-position: outside; + } + li.empty { + line-height: 30px; + vertical-align: middle; + background: #eee; + padding-left: 320px; + } + button.compose { + width: 150px; + } + .first-col, .second-col { + float: left; + min-height: 45px; + } + .first-col { + width: 150px; + } + .second-col { + width: 810px; + } + </style> +{% endblock %} +{% block profilesection %} + {% trans %}inbox - messages{% endtrans %} +{% endblock %} +{% block inbox_content %} + {% include "group_messaging/home.html" %} +{% endblock %} +{% block userjs %} + <script type="text/javascript" src="{{ 'js/group_messaging.js'|media }}"></script> +{% endblock %} diff --git a/askbot/urls.py b/askbot/urls.py index 39242a1b..362a16ee 100644 --- a/askbot/urls.py +++ b/askbot/urls.py @@ -513,6 +513,7 @@ urlpatterns = patterns('', {'domain': 'djangojs','packages': ('askbot',)}, name = 'askbot_jsi18n' ), + url('^messages/', include('group_messaging.urls')), ) #todo - this url below won't work, because it is defined above diff --git a/askbot/views/users.py b/askbot/views/users.py index 7a3bc116..dbcbda5c 100644 --- a/askbot/views/users.py +++ b/askbot/views/users.py @@ -709,7 +709,7 @@ def show_group_join_requests(request, user, context): } context.update(data) return render_into_skin('user_inbox/group_join_requests.html', context, request) - + @owner_or_moderator_required def user_responses(request, user, context): @@ -746,6 +746,26 @@ def user_responses(request, user, context): ) elif section == 'join_requests': return show_group_join_requests(request, user, context) + elif section == 'messages': + if request.user != user: + raise Http404 + #here we take shortcut, because we don't care about + #all the extra context loaded below + from group_messaging.views import SendersList, ThreadsList + context.update(SendersList().get_context(request)) + context.update(ThreadsList().get_context(request)) + data = { + 'active_tab':'users', + 'page_class': 'user-profile-page', + 'tab_name' : 'inbox', + 'inbox_section': section, + 'tab_description' : _('private messages'), + 'page_title' : _('profile - messages') + } + context.update(data) + return render_into_skin( + 'user_inbox/messages.html', context, request + ) else: raise Http404 diff --git a/group_messaging/urls.py b/group_messaging/urls.py new file mode 100644 index 00000000..eb033751 --- /dev/null +++ b/group_messaging/urls.py @@ -0,0 +1,22 @@ +"""url configuration for the group_messaging application""" +from django.conf.urls.defaults import patterns +from django.conf.urls.defaults import url +from group_messaging import views + +urlpatterns = patterns('', + url( + '^threads/$', + views.ThreadsList().as_view(), + name='get_threads' + ), + url( + '^threads/create/$', + views.NewThread().as_view(), + name='create_thread' + ), + url( + '^senders/$', + views.SendersList().as_view(), + name='get_senders' + ) +) diff --git a/group_messaging/views.py b/group_messaging/views.py index d24833f2..c2e9af93 100644 --- a/group_messaging/views.py +++ b/group_messaging/views.py @@ -8,92 +8,98 @@ in order to render messages within the page. Notice that :mod:`urls` module decorates all these functions and turns them into complete views """ -import functools -from django.contrib.auth.models import Group -from django.core.exceptions import PermissionDenied +from coffin.template.loader import get_template from django.forms import IntegerField from django.http import HttpResponse -from django.shortcuts import render_to_response -from django.template import RequestContext -from django.views.decorators.http import require_GET -from django.views.decorators.http import require_POST +from django.http import HttpResponseNotAllowed +from django.http import HttpResponseForbidden from django.utils import simplejson -from group_messages.models import Message -from group_messages.models import MessageMemo -from group_messages.models import SenderList -from group_messages.models import get_personal_group_by_id +from group_messaging.models import Message +from group_messaging.models import SenderList +from group_messaging.models import get_personal_group_by_user_id class InboxView(object): """custom class-based view to be used for pjax use and for generation - of content in the traditional way""" - template_name = None #this needs to be set + of content in the traditional way, where + the only the :method:`get_context` would be used. + """ + template_name = None #used only for the "GET" method + http_method_names = ('GET', 'POST') + + def render_to_response(self, context, template_name=None): + """like a django's shortcut, except will use + template_name from self, if `template_name` is not given. + Also, response is packaged as json with an html fragment + for the pjax consumption + """ + if template_name is None: + template_name = self.template_name + template = get_template(self.template_name) + html = template.render(context) + json = simplejson.dumps({'html': html}) + return HttpResponse(json, mimetype='application/json') + def get(self, request, *args, **kwargs): + """view function for the "GET" method""" context = self.get_context(request, *args, **kwargs) - #todo: load template with Coffin and render it - return HttpResponse(json, mimetype='application/json') + return self.render_to_response(context) + + def post(self, request, *args, **kwargs): + """view function for the "POST" method""" + pass + + def dispatch(self, request, *args, **kwargs): + """checks that the current request method is allowed + and calls the corresponding view function""" + if request.method not in self.http_method_names: + return HttpResponseNotAllowed() + view_func = getattr(self, request.method.lower()) + return view_func(request, *args, **kwargs) def get_context(self, request, *args, **kwargs): - """Should return the context dictionary""" + """Returns the context dictionary for the "get" + method only""" return {} - def as_pjax(self): + def as_view(self): """returns the view function - for the urls.py""" def view_function(request, *args, **kwargs): """the actual view function""" - if request.user.is_anonymous(): - raise PermissionDenied() - if request.is_ajax() is False: - raise PermissionDenied() - - if request.method == 'GET': - return self.get(request, *args, **kwargs) - elif request.method == 'POST': - return self.post(request, *args, **kwargs) + if request.user.is_authenticated() and request.is_ajax(): + view_method = getattr(self, request.method.lower()) + return view_method(request, *args, **kwargs) else: - raise NotImplementedError - return view_function - - -def require_login(view_func): - @functools.wraps(view_func) - def wrapped(request, *args, **kwargs): - if request.user.is_authenticated(): - return view_func(request, *args, **kwargs) - else: - raise PermissionDenied() - return wrapped + return HttpResponseForbidden() - -def ajax(view_func): - @functools.wraps(view_func) - def wrapped(request, *args, **kwargs): - if request.is_ajax(): - result = view_func(request, *args, **kwargs) - json = simplejson.dumps(result) - return HttpResponse(json, mimetype='application/json') - else: - raise PermissionDenied() + return view_function class NewThread(InboxView): - template_name = 'new_thread.html' + """view for creation of new thread""" + template_name = 'create_thread.html'# contains new thread form + http_method_list = ('GET', 'POST') def post(self, request): + """creates a new thread on behalf of the user + response is blank, because on the client side we just + need to go back to the thread listing view whose + content should be cached in the client' + """ recipient_id = IntegerField().clean(request.POST['recipient_id']) - recipient = get_personal_group_by_id(recipient_id) - message = Message.objects.create_thread( + recipient = get_personal_group_by_user_id(recipient_id) + Message.objects.create_thread( sender=request.user, recipients=[recipient], text=request.POST['text'] ) - return {'message_id': message.id} + return HttpResponse('', mimetype='application/json') class NewResponse(InboxView): - def get(self, request): - raise PermissionDenied() + """view to create a new response""" + http_method_list = ('POST',) def post(self, request): parent_id = IntegerField().clean(request.POST['parent_id']) @@ -103,30 +109,29 @@ class NewResponse(InboxView): text=request.POST['text'], parent=parent ) + return self.render_to_response( + {'message': message}, template_name='stored_message.htmtl' + ) class ThreadsList(InboxView): """shows list of threads for a given user""" template_name = 'threads_list.html' + http_method_list = ('GET',) def get_context(self, request): """returns thread list data""" - if request.method != 'GET': - raise PermissionDenied() - threads = Message.objects.get_threads_for_user(request.user) - threads = threads.values('id', 'headline', 'is_read') + threads = threads.values('id', 'headline') return {'threads': threads} class SendersList(InboxView): """shows list of senders for a user""" template_name = 'senders_list.html' + http_method_names = ('GET',) - def get_context(request): + def get_context(self, request): """get data about senders for the user""" - if request.method != 'GET': - raise PermissionDenied() - senders = SenderList.objects.get_senders_for_user(request.user) senders = senders.values('id', 'username') return {'senders': senders} @@ -135,12 +140,10 @@ class SendersList(InboxView): class ThreadDetails(InboxView): """shows entire thread in the unfolded form""" template_name = 'thread_details.html' + http_method_names = ('GET',) - def get_context(request): + def get_context(self, request): """shows individual thread""" - if request.method != 'GET': - raise PermissionDenied() - thread_id = IntegerField().clean(request.GET['thread_id']) #todo: assert that current thread is the root messages = Message.objects.filter(root__id=thread_id) |