Commit 9d8c6109 by Őry Máté

Merge branch 'feature-notification-2' into 'master'

Add a new Notification model

Fixes #66
parents 61cfbebd 55912feb
......@@ -7,7 +7,12 @@ from django.contrib.auth.signals import user_logged_in
from django.db.models import (
Model, ForeignKey, OneToOneField, CharField, IntegerField, TextField
)
from django.utils.translation import ugettext_lazy as _
from django.template.loader import render_to_string
from django.utils.translation import ugettext_lazy as _, override
from model_utils.models import TimeStampedModel
from model_utils.fields import StatusField
from model_utils import Choices
from vm.models import Instance
from acl.models import AclBase
......@@ -20,6 +25,32 @@ class Favourite(Model):
user = ForeignKey(User)
class Notification(TimeStampedModel):
STATUS = Choices(('new', _('new')),
('delivered', _('delivered')),
('read', _('read')))
status = StatusField()
to = ForeignKey(User)
subject = CharField(max_length=128)
message = TextField()
class Meta:
ordering = ['-created']
@classmethod
def send(cls, user, subject, template, context={}):
try:
language = user.profile.preferred_language
except:
language = None
with override(language):
context['user'] = user
rendered = render_to_string(template, context)
subject = unicode(subject)
return cls.objects.create(to=user, subject=subject, message=rendered)
class Profile(Model):
user = OneToOneField(User)
preferred_language = CharField(verbose_name=_('preferred language'),
......@@ -31,6 +62,9 @@ class Profile(Model):
help_text=_('Unique identifier of the person, e.g. a student number.'))
instance_limit = IntegerField(default=5)
def notify(self, subject, template, context={}):
return Notification.send(self.user, subject, template, context)
class GroupProfile(AclBase):
ACL_LEVELS = (
......
......@@ -331,3 +331,30 @@ a.hover-black {
display: block;
}
.notification-messages {
padding: 10px 8px;
width: 350px;
}
.notification-message {
margin-bottom: 10px;
padding: 0 0 4px 0;
border-bottom: 1px dotted #D3D3D3;
}
.notification-messages .notification-message:last-child {
margin-bottom: 0px;
padding: 0px;
border-bottom: none;
}
.notification-message-text {
padding: 8px 15px;
display: none;
}
.notification-message .notification-message-subject {
cursor: pointer;
}
......@@ -206,6 +206,15 @@ $(function () {
}
});
/* notification message toggle */
$(document).on('click', ".notification-message-subject", function() {
$(".notification-message-text", $(this).parent()).slideToggle();
return false;
});
$("#notification-button").click(function() {
$('.notification-messages').load("/dashboard/notifications/");
});
});
function generateVmHTML(pk, name, fav) {
......
{% load i18n %}
{% for n in notifications %}
<li class="notification-message">
<span class="notification-message-subject">
{% if n.status == "new" %}<i class="icon-envelope-alt"></i> {% endif %}
{{ n.subject }}
</span>
<span class="notification-message-date pull-right">
{{ n.created|timesince }}
</span>
<div style="clear: both;"></div>
<div class="notification-message-text">
{{ n.message }}
</div>
</li>
{% empty %}
<li class="notification-message">
{% trans "You have no notifications." %}
</li>
{% endfor %}
......@@ -23,8 +23,19 @@
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<a class="navbar-brand" href="/dashboard/">{% block header-site %}CIRCLE{% endblock %}</a>
<!-- temporarily -->
<div class="navbar-header">
<a class="navbar-brand" href="{% url "dashboard.index" %}">CIRCLE</a>
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div><!-- .navbar-header -->
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav pull-right">
{% block navbar-ul %}
{% endblock %}
</ul>
<a class="navbar-brand pull-right" href="/network/" style="color: white; font-size: 10px;">Network</a>
<a class="navbar-brand pull-right" href="/admin/" style="color: white; font-size: 10px;">Admin</a>
{% if user.is_authenticated %}
......@@ -32,7 +43,8 @@
{% else %}
<a class="navbar-brand pull-right" href="{% url "login" %}?next={% url "dashboard.index" %}" style="color: white; font-size: 10px;">Login</a>
{% endif %}
</div>
</div><!-- .collapse .navbar-collapse -->
</div><!-- navbar navbar-inverse navbar-fixed-top -->
<div class="container">
{% block messages %}
......
......@@ -3,6 +3,17 @@
{% block title-page %}{% trans "Dashboard" %}{% endblock %}
{% block navbar-ul %}
<li class="dropdown" id="notification-button">
<a href="{% url "dashboard.views.notifications" %}" style="color: white; font-size: 12px;" class="dropdown-toggle" data-toggle="dropdown">
Notifications{% if new_notifications %} <span class="badge">{{ new_notifications }}</span>{% endif %}
</a>
<ul class="dropdown-menu notification-messages">
<li>{% trans "Loading..." %}</li>
</ul>
</li>
{% endblock %}
{% block content %}
<div class="body-content dashboard-index">
<div class="row">
......
{% extends "dashboard/base.html" %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="no-margin"><i class="icon-desktop"></i> {% trans "Notifications" %}</h3>
</div>
<div class="panel-body">
<ul style="list-style: none;">
{% include "dashboard/_notifications-timeline.html" %}
</ul>
</div>
</div>
</div>
</div>
{% endblock %}
This is a test for {{user.username}}.
Var: {{var}}.
from django.contrib.auth.models import User
from django.test import TestCase
from ..models import Profile
class NotificationTestCase(TestCase):
def setUp(self):
self.u1 = User.objects.create(username='user1')
Profile.objects.get_or_create(user=self.u1)
self.u2 = User.objects.create(username='user2')
Profile.objects.get_or_create(user=self.u2)
def test_notification_send(self):
c1 = self.u1.notification_set.count()
c2 = self.u1.notification_set.count()
profile = self.u1.profile
msg = profile.notify('subj',
'dashboard/test_message.txt',
{'var': 'testme'})
assert self.u1.notification_set.count() == c1 + 1
assert self.u2.notification_set.count() == c2
assert 'user1' in msg.message
assert 'testme' in msg.message
assert msg in self.u1.notification_set.all()
......@@ -224,3 +224,13 @@ class VmDetailTest(TestCase):
'disk-size': 1})
self.assertEqual(response.status_code, 302)
self.assertEqual(disks + 1, inst.disks.count())
def test_notification_read(self):
c = Client()
self.login(c, "user1")
self.u1.profile.notify('subj', 'dashboard/test_message.txt',
{'var': 'testme'})
assert self.u1.notification_set.get().status == 'new'
response = c.get("/dashboard/notifications/")
self.assertEqual(response.status_code, 200)
assert self.u1.notification_set.get().status == 'read'
......@@ -8,7 +8,7 @@ from .views import (
TemplateList, LeaseDetail, NodeCreate, LeaseCreate, TemplateCreate,
FavouriteView, NodeStatus, GroupList, TemplateDelete, LeaseDelete,
VmGraphView, TemplateAclUpdateView, GroupDetailView, GroupDelete,
GroupAclUpdateView, GroupUserDelete, NodeGraphView
GroupAclUpdateView, GroupUserDelete, NotificationView, NodeGraphView,
)
urlpatterns = patterns(
......@@ -81,4 +81,7 @@ urlpatterns = patterns(
name='dashboard.views.group-acl'),
url(r'^groupuser/delete/(?P<pk>\d+)/$', GroupUserDelete.as_view(),
name="dashboard.views.delete-groupuser"),
url(r'^notifications/$', NotificationView.as_view(),
name="dashboard.views.notifications"),
)
......@@ -81,6 +81,10 @@ class IndexView(LoginRequiredMixin, TemplateView):
'more_instances': instances.count() - len(instances[:5])
})
if user is not None:
context['new_notifications'] = user.notification_set.filter(
status="new").count()
nodes = Node.objects.all()
groups = Group.objects.all()
context.update({
......@@ -1652,3 +1656,31 @@ class NodeGraphView(SuperuserRequiredMixin, GraphViewBase):
def get_object(self, request, pk):
return self.model.objects.get(id=pk)
class NotificationView(LoginRequiredMixin, TemplateView):
def get_template_names(self):
if self.request.is_ajax():
return ['dashboard/_notifications-timeline.html']
else:
return ['dashboard/notifications.html']
def get_context_data(self, *args, **kwargs):
context = super(NotificationView, self).get_context_data(
*args, **kwargs)
# we need to convert it to list, otherwise it's gonna be
# similar to a QuerySet and update everything to
# read status after get
n = 10 if self.request.is_ajax() else 1000
context['notifications'] = list(
self.request.user.notification_set.values()[:n])
return context
def get(self, *args, **kwargs):
response = super(NotificationView, self).get(*args, **kwargs)
un = self.request.user.notification_set.filter(status="new")
for u in un:
u.status = "read"
u.save()
return response
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment