Merge branch 'notifications'

Conflicts:
	greenmine/scrum/api.py
	greenmine/scrum/models.py
remotes/origin/enhancement/email-actions
David Barragán Merino 2013-07-17 11:11:25 +02:00
commit 50566807c0
58 changed files with 417 additions and 143 deletions

View File

@ -11,6 +11,7 @@ Setup development environment.
python manage.py syncdb --migrate --noinput python manage.py syncdb --migrate --noinput
python manage.py loaddata initial_user python manage.py loaddata initial_user
python manage.py sample_data python manage.py sample_data
python manage.py createinitialrevisions
Auth: admin/123123 Auth: admin/123123

View File

@ -11,6 +11,7 @@ from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import UserManager, AbstractUser, Group from django.contrib.auth.models import UserManager, AbstractUser, Group
from greenmine.scrum.models import Project, UserStory, Task from greenmine.scrum.models import Project, UserStory, Task
from greenmine.base.notifications.models import WatcherMixin
import uuid import uuid
@ -19,7 +20,6 @@ import uuid
@receiver(signals.pre_save) @receiver(signals.pre_save)
def attach_uuid(sender, instance, **kwargs): def attach_uuid(sender, instance, **kwargs):
fields = sender._meta.init_name_map() fields = sender._meta.init_name_map()
#fields = sender._meta.get_all_field_names()
if 'modified_date' in fields: if 'modified_date' in fields:
instance.modified_date = now() instance.modified_date = now()
@ -30,8 +30,7 @@ def attach_uuid(sender, instance, **kwargs):
instance.uuid = unicode(uuid.uuid1()) instance.uuid = unicode(uuid.uuid1())
class User(AbstractUser, WatcherMixin):
class User(AbstractUser):
color = models.CharField(max_length=9, null=False, blank=False, default="#669933", color = models.CharField(max_length=9, null=False, blank=False, default="#669933",
verbose_name=_('color')) verbose_name=_('color'))
description = models.TextField(null=False, blank=True, description = models.TextField(null=False, blank=True,

View File

@ -1,4 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import print_function
import sys
from rest_framework import views from rest_framework import views
from rest_framework import status, exceptions from rest_framework import status, exceptions
@ -27,5 +29,5 @@ def patch_api_view():
view.cls_instance = cls(**initkwargs) view.cls_instance = cls(**initkwargs)
return view return view
print "Patching APIView" print("Patching APIView", file=sys.stderr)
views.APIView = APIView views.APIView = APIView

View File

View File

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-i
from djmail import template_mail
class NotificationSenderMixin(object):
create_notification_template = None
update_notification_template = None
destroy_notification_template = None
def _send_notification_email(template_method, users=None, context=None):
mails = template_mail.MagicMailBuilder()
for user in users:
email = getattr(mails, template_method)(user, context)
email.send()
def post_save(self, obj, created=False):
users = obj.get_watchers_to_notify(self.request.user)
context = {
'changer': self.request.user,
'changed_fields_dict': obj.get_changed_fields_dict(self.request.DATA),
'object': obj
}
if created:
#self._send_notification_email(self.create_notification_template, users=users, context=context)
print "TODO: Send the notification email of object creation"
else:
#self._send_notification_email(self.update_notification_template, users=users, context=context)
print "TODO: Send the notification email of object modification"
def destroy(self, request, *args, **kwargs):
users = obj.get_watchers_to_notify(self.request.user)
context = {
'changer': self.request.user,
'object': obj
}
#self._send_notification_email(self.destroy_notification_template, users=users, context=context)
print "TODO: Send the notification email of object deletion"
return super(NotificationSenderMixin, self).destroy(request, *args, **kwargs)

View File

@ -0,0 +1,134 @@
# -*- coding: utf-8 -*-
from django.db import models
from django.db.models.fields import FieldDoesNotExist
from django.utils.translation import ugettext_lazy as _
import reversion
class WatcherMixin(object):
NOTIFY_LEVEL_CHOICES = (
("all_owned_projects", _(u"All events on my projects")),
("only_watching", _(u"Only events for objects i watch")),
("only_assigned", _(u"Only events for objects assigned to me")),
("only_owner", _(u"Only events for objects owned by me")),
("no_events", _(u"No events")),
)
notify_level = models.CharField(max_length=32, null=False, blank=False, default="only_watching",
choices=NOTIFY_LEVEL_CHOICES, verbose_name=_(u"notify level"))
notify_changes_by_me = models.BooleanField(null=False, blank=True,
verbose_name=_(u"notify changes made by me"))
class Meta:
abstract = True
def allow_notify_owned(self):
return (self.notify_level in [
"only_owner",
"only_assigned",
"only_watching",
"all_owned_projects",
])
def allow_notify_assigned_to(self):
return (self.notify_level in [
"only_assigned",
"only_watching",
"all_owned_projects",
])
def allow_notify_suscribed(self):
return (self.notify_level in [
"only_watching",
"all_owned_projects",
])
def allow_notify_project(self, project):
return self.notify_level == "all_owned_projects" and project.owner.pk == self.pk
def allow_notify_by_me(self, changer):
return (changer.pk != self.pk) or self.notify_changes_by_me
class WatchedMixin(object):
class Meta:
abstract = True
@property
def last_version(self):
version_list = reversion.get_for_object(self)
return version_list and version_list[0] or None
def get_changed_fields_dict(self, data_dict):
field_dict = {}
for field_name, data_value in data_dict.items():
field_dict.update(self._get_changed_field(field_name, data_value))
return field_dict
def get_watchers_to_notify(self, changer):
watchers_to_notify = set()
watchers_by_role = self._get_watchers_by_role()
owner = watchers_by_role.get("owner")
if (owner and owner.allow_notify_owned()
and owner.allow_notify_by_me(changer)):
watchers_to_notify.add(owner)
assigned_to = watchers_by_role.get("assigned_to")
if (assigned_to and assigned_to.allow_notify_assigned_to()
and assigned_to.allow_notify_by_me(changer)):
watchers_to_notify.add(assigned_to)
suscribed_watchers = watchers_by_role.get("suscribed_watchers")
if suscribed_watchers:
for suscribed_watcher in suscribed_watchers:
if (suscribed_watcher and suscribed_watcher.allow_notify_suscribed()
and suscribed_watcher.allow_notify_by_me(changer)):
watchers_to_notify.add(suscribed_watcher)
#(project, project_owner) = watchers_by_role.get("project_owner")
#if project_owner \
# and project_owner.allow_notify_project(project) \
# and project_owner.allow_notify_by_me(self._changer):
# watchers_to_notify.add(project_owner)
return watchers_to_notify
def _get_changed_field_verbose_name(self, field_name):
try:
return self._meta.get_field(field_name).verbose_name
except FieldDoesNotExist:
return field_name
def _get_changed_field_old_value(self, field_name, data_value):
return (self.last_version and self.last_version.field_dict.get(field_name, data_value) or None)
def _get_changed_field_new_value(self, field_name, data_value):
return getattr(self, field_name, data_value)
def _get_changed_field(self, field_name, data_value):
verbose_name = self._get_changed_field_verbose_name(field_name)
old_value = self._get_changed_field_old_value(field_name, data_value)
new_value = self._get_changed_field_new_value(field_name, data_value)
return {field_name: {
"verbose_name": verbose_name,
"old_value": old_value,
"new_value": new_value,
}}
def _get_watchers_by_role(self):
"""
Return the actual instances of watchers of this object, classified by role.
For example:
return {
"owner": self.owner,
"assigned_to": self.assigned_to,
"suscribed_watchers": self.watchers.all(),
"project_owner": (self.project, self.project.owner),
}
"""
raise NotImplementedError("You must subclass WatchedMixin and provide _get_watchers_by_role method")

View File

@ -7,10 +7,13 @@ from rest_framework import generics
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from greenmine.base.models import * from greenmine.base.models import *
from greenmine.base.notifications.api import NotificationSenderMixin
from greenmine.scrum.serializers import * from greenmine.scrum.serializers import *
from greenmine.scrum.models import * from greenmine.scrum.models import *
from greenmine.scrum.permissions import * from greenmine.scrum.permissions import *
class UserStoryFilter(django_filters.FilterSet): class UserStoryFilter(django_filters.FilterSet):
no_milestone = django_filters.NumberFilter(name="milestone", lookup_type='isnull') no_milestone = django_filters.NumberFilter(name="milestone", lookup_type='isnull')
@ -47,10 +50,13 @@ class SimpleFilterMixin(object):
return queryset return queryset
class ProjectList(generics.ListCreateAPIView): class ProjectList(NotificationSenderMixin, generics.ListCreateAPIView):
model = Project model = Project
serializer_class = ProjectSerializer serializer_class = ProjectSerializer
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
create_notification_template = "create_project_notification"
update_notification_template = "update_project_notification"
destroy_notification_template = "destroy_project_notification"
def get_queryset(self): def get_queryset(self):
return self.model.objects.filter( return self.model.objects.filter(
@ -61,17 +67,23 @@ class ProjectList(generics.ListCreateAPIView):
obj.owner = self.request.user obj.owner = self.request.user
class ProjectDetail(generics.RetrieveUpdateDestroyAPIView): class ProjectDetail(NotificationSenderMixin, generics.RetrieveUpdateDestroyAPIView):
model = Project model = Project
serializer_class = ProjectSerializer serializer_class = ProjectSerializer
permission_classes = (IsAuthenticated, ProjectDetailPermission,) permission_classes = (IsAuthenticated, ProjectDetailPermission,)
create_notification_template = "create_project_notification"
update_notification_template = "update_project_notification"
destroy_notification_template = "destroy_project_notification"
class MilestoneList(generics.ListCreateAPIView): class MilestoneList(NotificationSenderMixin, generics.ListCreateAPIView):
model = Milestone model = Milestone
serializer_class = MilestoneSerializer serializer_class = MilestoneSerializer
filter_fields = ('project',) filter_fields = ('project',)
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
create_notification_template = "create_milestone_notification"
update_notification_template = "update_milestone_notification"
destroy_notification_template = "destroy_milestone_notification"
def get_queryset(self): def get_queryset(self):
return self.model.objects.filter(project__members=self.request.user) return self.model.objects.filter(project__members=self.request.user)
@ -80,17 +92,23 @@ class MilestoneList(generics.ListCreateAPIView):
obj.owner = self.request.user obj.owner = self.request.user
class MilestoneDetail(generics.RetrieveUpdateDestroyAPIView): class MilestoneDetail(NotificationSenderMixin, generics.RetrieveUpdateDestroyAPIView):
model = Milestone model = Milestone
serializer_class = MilestoneSerializer serializer_class = MilestoneSerializer
permission_classes = (IsAuthenticated, MilestoneDetailPermission,) permission_classes = (IsAuthenticated, MilestoneDetailPermission,)
create_notification_template = "create_milestone_notification"
update_notification_template = "update_milestone_notification"
destroy_notification_template = "destroy_milestone_notification"
class UserStoryList(generics.ListCreateAPIView): class UserStoryList(NotificationSenderMixin, generics.ListCreateAPIView):
model = UserStory model = UserStory
serializer_class = UserStorySerializer serializer_class = UserStorySerializer
filter_class = UserStoryFilter filter_class = UserStoryFilter
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
create_notification_template = "create_user_story_notification"
update_notification_template = "update_user_story_notification"
destroy_notification_template = "destroy_user_story_notification"
def get_queryset(self): def get_queryset(self):
return self.model.objects.filter(project__members=self.request.user) return self.model.objects.filter(project__members=self.request.user)
@ -99,10 +117,13 @@ class UserStoryList(generics.ListCreateAPIView):
obj.owner = self.request.user obj.owner = self.request.user
class UserStoryDetail(generics.RetrieveUpdateDestroyAPIView): class UserStoryDetail(NotificationSenderMixin, generics.RetrieveUpdateDestroyAPIView):
model = UserStory model = UserStory
serializer_class = UserStorySerializer serializer_class = UserStorySerializer
permission_classes = (IsAuthenticated, UserStoryDetailPermission,) permission_classes = (IsAuthenticated, UserStoryDetailPermission,)
create_notification_template = "create_user_story_notification"
update_notification_template = "update_user_story_notification"
destroy_notification_template = "destroy_user_story_notification"
class AttachmentFilter(django_filters.FilterSet): class AttachmentFilter(django_filters.FilterSet):
@ -157,11 +178,14 @@ class TasksAttachmentDetail(generics.RetrieveUpdateDestroyAPIView):
permission_classes = (IsAuthenticated, AttachmentDetailPermission,) permission_classes = (IsAuthenticated, AttachmentDetailPermission,)
class TaskList(generics.ListCreateAPIView): class TaskList(NotificationSenderMixin, generics.ListCreateAPIView):
model = Task model = Task
serializer_class = TaskSerializer serializer_class = TaskSerializer
filter_fields = ('user_story', 'milestone', 'project') filter_fields = ('user_story', 'milestone', 'project')
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
create_notification_template = "create_task_notification"
update_notification_template = "update_task_notification"
destroy_notification_template = "destroy_task_notification"
def get_queryset(self): def get_queryset(self):
return self.model.objects.filter(project__members=self.request.user) return self.model.objects.filter(project__members=self.request.user)
@ -171,23 +195,30 @@ class TaskList(generics.ListCreateAPIView):
obj.milestone = obj.user_story.milestone obj.milestone = obj.user_story.milestone
class TaskDetail(generics.RetrieveUpdateDestroyAPIView): class TaskDetail(NotificationSenderMixin, generics.RetrieveUpdateDestroyAPIView):
model = Task model = Task
serializer_class = TaskSerializer serializer_class = TaskSerializer
permission_classes = (IsAuthenticated, TaskDetailPermission,) permission_classes = (IsAuthenticated, TaskDetailPermission,)
create_notification_template = "create_task_notification"
update_notification_template = "update_task_notification"
destroy_notification_template = "destroy_task_notification"
def post_save(self, obj, created=False): def post_save(self, obj, created=False):
with reversion.create_revision(): with reversion.create_revision():
if "comment" in self.request.DATA: if "comment" in self.request.DATA:
# Update the comment in the last version # Update the comment in the last version
reversion.set_comment(self.request.DATA['comment']) reversion.set_comment(self.request.DATA['comment'])
super(TaskDetail, self).post_save(obj, created)
class IssueList(generics.ListCreateAPIView): class IssueList(NotificationSenderMixin, generics.ListCreateAPIView):
model = Issue model = Issue
serializer_class = IssueSerializer serializer_class = IssueSerializer
filter_fields = ('project',) filter_fields = ('project',)
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
create_notification_template = "create_issue_notification"
update_notification_template = "update_issue_notification"
destroy_notification_template = "destroy_issue_notification"
def pre_save(self, obj): def pre_save(self, obj):
obj.owner = self.request.user obj.owner = self.request.user
@ -196,16 +227,20 @@ class IssueList(generics.ListCreateAPIView):
return self.model.objects.filter(project__members=self.request.user) return self.model.objects.filter(project__members=self.request.user)
class IssueDetail(generics.RetrieveUpdateDestroyAPIView): class IssueDetail(NotificationSenderMixin, generics.RetrieveUpdateDestroyAPIView):
model = Issue model = Issue
serializer_class = IssueSerializer serializer_class = IssueSerializer
permission_classes = (IsAuthenticated, IssueDetailPermission,) permission_classes = (IsAuthenticated, IssueDetailPermission,)
create_notification_template = "create_issue_notification"
update_notification_template = "update_issue_notification"
destroy_notification_template = "destroy_issue_notification"
def post_save(self, obj, created=False): def post_save(self, obj, created=False):
with reversion.create_revision(): with reversion.create_revision():
if "comment" in self.request.DATA: if "comment" in self.request.DATA:
# Update the comment in the last version # Update the comment in the last version
reversion.set_comment(self.request.DATA['comment']) reversion.set_comment(self.request.DATA['comment'])
super(IssueDetail, self).post_save(obj, created)
class SeverityList(generics.ListCreateAPIView): class SeverityList(generics.ListCreateAPIView):

View File

@ -10,12 +10,24 @@ from django.db.models.loading import get_model
from picklefield.fields import PickledObjectField from picklefield.fields import PickledObjectField
from greenmine.base.utils.slug import slugify_uniquely, ref_uniquely from greenmine.base.utils.slug import (
slugify_uniquely,
ref_uniquely
)
from greenmine.base.utils import iter_points from greenmine.base.utils import iter_points
from greenmine.scrum.choices import (ISSUESTATUSES, TASKSTATUSES, USSTATUSES, from greenmine.base.notifications.models import WatchedMixin
POINTS_CHOICES, SEVERITY_CHOICES, from greenmine.scrum.choices import (
ISSUETYPES, TASK_CHANGE_CHOICES, ISSUESTATUSES,
PRIORITY_CHOICES) TASKSTATUSES,
USSTATUSES,
POINTS_CHOICES,
SEVERITY_CHOICES,
ISSUETYPES,
TASK_CHANGE_CHOICES,
PRIORITY_CHOICES
)
import reversion
class Severity(models.Model): class Severity(models.Model):
@ -173,7 +185,7 @@ class Membership(models.Model):
unique_together = ('user', 'project') unique_together = ('user', 'project')
class Project(models.Model): class Project(models.Model, WatchedMixin):
uuid = models.CharField(max_length=40, unique=True, null=False, blank=True, uuid = models.CharField(max_length=40, unique=True, null=False, blank=True,
verbose_name=_('uuid')) verbose_name=_('uuid'))
name = models.CharField(max_length=250, unique=True, null=False, blank=False, name = models.CharField(max_length=250, unique=True, null=False, blank=False,
@ -228,17 +240,30 @@ class Project(models.Model):
super(Project, self).save(*args, **kwargs) super(Project, self).save(*args, **kwargs)
def _get_watchers_by_role(self):
return {'owner': self.owner}
def eget_attrinutes_to_notify(self):
return {
'name': self.name,
'slug': self.slug,
'description': self.description,
'modified_date': self.modified_date,
'owner': self.owner.get_full_name(),
'members': ', '.join([member.get_full_name() for member in self.members.all()]),
'public': self.public,
'tags': self.tags,
}
@property @property
def list_of_milestones(self): def list_of_milestones(self):
return [ return [{
{ 'name': milestone.name,
'name': milestone.name, 'finish_date': milestone.estimated_finish,
'finish_date': milestone.estimated_finish, 'closed_points': milestone.closed_points,
'closed_points': milestone.closed_points, 'client_increment_points': milestone.client_increment_points,
'client_increment_points': milestone.client_increment_points, 'team_increment_points': milestone.team_increment_points
'team_increment_points': milestone.team_increment_points } for milestone in self.milestones.all().order_by('estimated_start')]
} for milestone in self.milestones.all().order_by('estimated_start')
]
@property @property
def list_roles(self): def list_roles(self):
@ -268,43 +293,35 @@ class Project(models.Model):
.exclude(role__id__in=role_ids)\ .exclude(role__id__in=role_ids)\
.delete() .delete()
class Milestone(models.Model): def _get_watchers_by_role(self):
uuid = models.CharField( return {'owner': self.owner}
max_length=40, unique=True, null=False, blank=True,
verbose_name=_('uuid'))
name = models.CharField(
max_length=200, db_index=True, null=False, blank=False,
verbose_name=_('name'))
class Milestone(models.Model, WatchedMixin):
uuid = models.CharField(max_length=40, unique=True, null=False, blank=True,
verbose_name=_('uuid'))
name = models.CharField(max_length=200, db_index=True, null=False, blank=False,
verbose_name=_('name'))
slug = models.SlugField(max_length=250, unique=True, null=False, blank=True, slug = models.SlugField(max_length=250, unique=True, null=False, blank=True,
verbose_name=_('slug')) verbose_name=_('slug'))
owner = models.ForeignKey('base.User', null=True, blank=True, related_name='owned_milestones',
owner = models.ForeignKey( verbose_name=_('owner'))
'base.User', project = models.ForeignKey('Project', null=False, blank=False, related_name='milestones',
null=True, blank=True, verbose_name=_('project'))
related_name='owned_milestones', verbose_name=_('owner'))
project = models.ForeignKey(
'Project',
null=False, blank=False,
related_name='milestones',
verbose_name=_('project'))
estimated_start = models.DateField(null=True, blank=True, default=None, estimated_start = models.DateField(null=True, blank=True, default=None,
verbose_name=_('estimated start')) verbose_name=_('estimated start'))
estimated_finish = models.DateField(null=True, blank=True, default=None, estimated_finish = models.DateField(null=True, blank=True, default=None,
verbose_name=_('estimated finish')) verbose_name=_('estimated finish'))
created_date = models.DateTimeField(auto_now_add=True, null=False, blank=False, created_date = models.DateTimeField(auto_now_add=True, null=False, blank=False,
verbose_name=_('created date')) verbose_name=_('created date'))
modified_date = models.DateTimeField(auto_now=True, null=False, blank=False, modified_date = models.DateTimeField(auto_now=True, null=False, blank=False,
verbose_name=_('modified date')) verbose_name=_('modified date'))
closed = models.BooleanField(default=False, null=False, blank=True, closed = models.BooleanField(default=False, null=False, blank=True,
verbose_name=_('is closed')) verbose_name=_('is closed'))
disponibility = models.FloatField(default=0.0, null=True, blank=True, disponibility = models.FloatField(default=0.0, null=True, blank=True,
verbose_name=_('disponibility')) verbose_name=_('disponibility'))
order = models.PositiveSmallIntegerField(default=1, null=False, blank=False, order = models.PositiveSmallIntegerField(default=1, null=False, blank=False,
verbose_name=_('order')) verbose_name=_('order'))
class Meta: class Meta:
verbose_name = u'milestone' verbose_name = u'milestone'
@ -373,6 +390,11 @@ class Milestone(models.Model):
#return sum(points) #return sum(points)
return 0 return 0
def _get_watchers_by_role(self):
return {
'owner': self.owner,
'project_owner': (self.project, self.project.owner),
}
class RolePoints(models.Model): class RolePoints(models.Model):
user_story = models.ForeignKey('UserStory', null=False, blank=False, user_story = models.ForeignKey('UserStory', null=False, blank=False,
@ -389,7 +411,7 @@ class RolePoints(models.Model):
unique_together = ('user_story', 'role') unique_together = ('user_story', 'role')
class UserStory(models.Model): class UserStory(WatchedMixin, models.Model):
uuid = models.CharField(max_length=40, unique=True, null=False, blank=True, uuid = models.CharField(max_length=40, unique=True, null=False, blank=True,
verbose_name=_('uuid')) verbose_name=_('uuid'))
ref = models.BigIntegerField(db_index=True, null=True, blank=True, default=None, ref = models.BigIntegerField(db_index=True, null=True, blank=True, default=None,
@ -458,6 +480,13 @@ class UserStory(models.Model):
def get_role_points(self): def get_role_points(self):
return self.role_points return self.role_points
def _get_watchers_by_role(self):
return {
'owner': self.owner,
'suscribed_watchers': self.watchers.all(),
'project_owner': (self.project, self.project.owner),
}
class Attachment(models.Model): class Attachment(models.Model):
owner = models.ForeignKey('base.User', null=False, blank=False, owner = models.ForeignKey('base.User', null=False, blank=False,
@ -489,7 +518,7 @@ class Attachment(models.Model):
self.content_type, self.object_id, self.id) self.content_type, self.object_id, self.id)
class Task(models.Model): class Task(models.Model, WatchedMixin):
uuid = models.CharField(max_length=40, unique=True, null=False, blank=True, uuid = models.CharField(max_length=40, unique=True, null=False, blank=True,
verbose_name=_('uuid')) verbose_name=_('uuid'))
user_story = models.ForeignKey('UserStory', null=True, blank=False, user_story = models.ForeignKey('UserStory', null=True, blank=False,
@ -555,8 +584,16 @@ class Task(models.Model):
super(Task, self).save(*args, **kwargs) super(Task, self).save(*args, **kwargs)
def _get_watchers_by_role(self):
return {
'owner': self.owner,
'assigned_to': self.assigned_to,
'suscribed_watchers': self.watchers.all(),
'project_owner': (self.project, self.project.owner),
}
class Issue(models.Model):
class Issue(models.Model, WatchedMixin):
uuid = models.CharField(max_length=40, unique=True, null=False, blank=True, uuid = models.CharField(max_length=40, unique=True, null=False, blank=True,
verbose_name=_('uuid')) verbose_name=_('uuid'))
ref = models.BigIntegerField(db_index=True, null=True, blank=True, default=None, ref = models.BigIntegerField(db_index=True, null=True, blank=True, default=None,
@ -629,6 +666,23 @@ class Issue(models.Model):
def is_closed(self): def is_closed(self):
return self.status.is_closed return self.status.is_closed
def _get_watchers_by_role(self):
return {
'owner': self.owner,
'assigned_to': self.assigned_to,
'suscribed_watchers': self.watchers.all(),
'project_owner': (self.project, self.project.owner),
}
# Reversion registration (usufull for base.notification and for meke a historical)
reversion.register(Project)
reversion.register(Milestone)
reversion.register(UserStory)
reversion.register(Task)
reversion.register(Issue)
# Model related signals handlers # Model related signals handlers
@ -716,7 +770,3 @@ def tasks_close_handler(sender, instance, **kwargs):
else: else:
instance.user_story.finish_date = None instance.user_story.finish_date = None
instance.user_story.save() instance.user_story.save()
# Email alerts signals handlers
# TODO: temporary commented (Pending refactor)
# from . import sigdispatch

View File

@ -32,74 +32,75 @@ def mail_recovery_password(sender, user, **kwargs):
subject = ugettext("Greenmine: password recovery.") subject = ugettext("Greenmine: password recovery.")
send_mail.delay(subject, template, [user.email]) send_mail.delay(subject, template, [user.email])
## TODO: Remove me when base.notifications is finished
@receiver(signals.mail_milestone_created) ##
def mail_milestone_created(sender, milestone, user, **kwargs): #@receiver(signals.mail_milestone_created)
participants = milestone.project.all_participants() #def mail_milestone_created(sender, milestone, user, **kwargs):
# participants = milestone.project.all_participants()
emails_list = [] #
subject = ugettext("Greenmine: sprint created") # emails_list = []
for person in participants: # subject = ugettext("Greenmine: sprint created")
template = render_to_string("email/milestone.created.html", { # for person in participants:
"person": person, # template = render_to_string("email/milestone.created.html", {
"current_host": settings.HOST, # "person": person,
"milestone": milestone, # "current_host": settings.HOST,
"user": user, # "milestone": milestone,
}) # "user": user,
# })
emails_list.append([subject, template, [person.email]]) #
# emails_list.append([subject, template, [person.email]])
send_bulk_mail.delay(emails_list) #
# send_bulk_mail.delay(emails_list)
#
@receiver(signals.mail_userstory_created) #
def mail_userstory_created(sender, us, user, **kwargs): #@receiver(signals.mail_userstory_created)
participants = us.milestone.project.all_participants() #def mail_userstory_created(sender, us, user, **kwargs):
# participants = us.milestone.project.all_participants()
emails_list = [] #
subject = ugettext("Greenmine: user story created") # emails_list = []
# subject = ugettext("Greenmine: user story created")
for person in participants: #
template = render_to_string("email/userstory.created.html", { # for person in participants:
"person": person, # template = render_to_string("email/userstory.created.html", {
"current_host": settings.HOST, # "person": person,
"us": us, # "current_host": settings.HOST,
"user": user, # "us": us,
}) # "user": user,
# })
emails_list.append([subject, template, [person.email]]) #
# emails_list.append([subject, template, [person.email]])
send_bulk_mail.delay(emails_list) #
# send_bulk_mail.delay(emails_list)
#
@receiver(signals.mail_task_created) #
def mail_task_created(sender, task, user, **kwargs): #@receiver(signals.mail_task_created)
participants = task.us.milestone.project.all_participants() #def mail_task_created(sender, task, user, **kwargs):
# participants = task.us.milestone.project.all_participants()
emails_list = [] #
subject = ugettext("Greenmine: task created") # emails_list = []
# subject = ugettext("Greenmine: task created")
for person in participants: #
template = render_to_string("email/task.created.html", { # for person in participants:
"person": person, # template = render_to_string("email/task.created.html", {
"current_host": settings.HOST, # "person": person,
"task": task, # "current_host": settings.HOST,
"user": user, # "task": task,
}) # "user": user,
# })
emails_list.append([subject, template, [person.email]]) #
# emails_list.append([subject, template, [person.email]])
send_bulk_mail.delay(emails_list) #
# send_bulk_mail.delay(emails_list)
#
@receiver(signals.mail_task_assigned) #
def mail_task_assigned(sender, task, user, **kwargs): #@receiver(signals.mail_task_assigned)
template = render_to_string("email/task.assigned.html", { #def mail_task_assigned(sender, task, user, **kwargs):
"person": task.assigned_to, # template = render_to_string("email/task.assigned.html", {
"task": task, # "person": task.assigned_to,
"user": user, # "task": task,
"current_host": settings.HOST, # "user": user,
}) # "current_host": settings.HOST,
# })
subject = ugettext("Greenmine: task assigned") #
send_mail.delay(subject, template, [task.assigned_to.email]) # subject = ugettext("Greenmine: task assigned")
# send_mail.delay(subject, template, [task.assigned_to.email])

View File

@ -1,11 +1,13 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import (
from __future__ import absolute_import absolute_import,
import os print_function
)
import os, sys
try: try:
print "Trying import local.py settings..." print("Trying import local.py settings...", file=sys.stderr)
from .local import * from .local import *
except ImportError: except ImportError:
print "Trying import development.py settings..." print("Trying import development.py settings...", file=sys.stderr)
from .development import * from .development import *

View File

@ -201,6 +201,8 @@ INSTALLED_APPS = [
'greenmine.base', 'greenmine.base',
'greenmine.base.mail', 'greenmine.base.mail',
'greenmine.base.notifications',
'greenmine.base.notifications.email',
'greenmine.scrum', 'greenmine.scrum',
'greenmine.wiki', 'greenmine.wiki',
'greenmine.documents', 'greenmine.documents',

View File

@ -1,8 +1,14 @@
#!/bin/bash #!/bin/bash
# For sqlite
rm -f database.sqlite
# For postgresql
dropdb greenmine dropdb greenmine
createdb greenmine createdb greenmine
python manage.py syncdb --migrate --noinput python manage.py syncdb --migrate --noinput --traceback
python manage.py loaddata initial_user python manage.py loaddata initial_user --traceback
python manage.py sample_data python manage.py sample_data --traceback
python manage.py createinitialrevisions --traceback

View File

@ -17,7 +17,6 @@ git+git://github.com/toastdriven/django-haystack.git
django-picklefield==0.3.0 django-picklefield==0.3.0
django-reversion==1.7 django-reversion==1.7
django-sampledatahelper==0.0.1 django-sampledatahelper==0.0.1
django-tastypie==0.9.14
djangorestframework==2.2.5 djangorestframework==2.2.5
gunicorn==17.5 gunicorn==17.5
kombu==2.5.12 kombu==2.5.12
@ -28,3 +27,5 @@ pycrypto==2.6
python-dateutil==2.1 python-dateutil==2.1
pytz==2013b pytz==2013b
six==1.3.0 six==1.3.0
djmail>=0.1