summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--askbot/skins/common/media/js/group_messaging.js186
-rw-r--r--askbot/skins/default/media/style/style.less2
-rw-r--r--askbot/skins/default/templates/group_messaging/home.html12
-rw-r--r--askbot/skins/default/templates/group_messaging/senders_list.html9
-rw-r--r--askbot/skins/default/templates/group_messaging/threads_list.html13
-rw-r--r--askbot/skins/default/templates/user_inbox/base.html6
-rw-r--r--askbot/skins/default/templates/user_inbox/messages.html42
-rw-r--r--askbot/urls.py1
-rw-r--r--askbot/views/users.py22
-rw-r--r--group_messaging/urls.py22
-rw-r--r--group_messaging/views.py135
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&section=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&section=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)