Merge pull request #144 from taigaio/notification-refactor

Notification refactor
remotes/origin/enhancement/email-actions
Jesús Espino 2014-10-29 12:41:39 +01:00
commit dc90036e55
77 changed files with 481 additions and 308 deletions

View File

@ -332,6 +332,10 @@ TAGS_PREDEFINED_COLORS = ["#fce94f", "#edd400", "#c4a000", "#8ae234",
FEEDBACK_ENABLED = True
FEEDBACK_EMAIL = "support@taiga.io"
# 0 notifications will work in a synchronous way
# >0 an external process will check the pending notifications and will send them
# collapsed during that interval
CHANGE_NOTIFICATIONS_MIN_INTERVAL = 0 #seconds
# NOTE: DON'T INSERT MORE SETTINGS AFTER THIS LINE
TEST_RUNNER="django.test.runner.DiscoverRunner"

View File

@ -31,7 +31,7 @@ from taiga.base import filters
from taiga.base import exceptions as exc
from taiga.users.models import User
from taiga.projects.notifications import WatchedResourceMixin
from taiga.projects.notifications.mixins import WatchedResourceMixin
from taiga.projects.history.mixins import HistoryResourceMixin
from . import permissions

View File

@ -76,6 +76,18 @@ class HistoryEntry(models.Model):
# snapshot. The rest are partial snapshot.
is_snapshot = models.BooleanField(default=False)
@cached_property
def is_change(self):
return self.type == HistoryType.change
@cached_property
def is_create(self):
return self.type == HistoryType.create
@cached_property
def is_delete(self):
return self.type == HistoryType.delete
@cached_property
def owner(self):
pk = self.user["pk"]
@ -198,4 +210,3 @@ class HistoryEntry(models.Model):
class Meta:
ordering = ["created_at"]

View File

@ -73,6 +73,14 @@ def make_key_from_model_object(obj:object) -> str:
return "{0}:{1}".format(tn, obj.pk)
def get_model_from_key(key:str) -> object:
"""
Get model from key
"""
class_name, pk = key.split(":", 1)
return apps.get_model(class_name)
def register_values_implementation(typename:str, fn=None):
"""
Register values implementation for specified typename.

View File

@ -99,12 +99,12 @@
{# DESCRIPTIONS #}
{% elif field_name in ["description_diff"] %}
<dd style="background: #eee; padding: 5px 15px; color: #444">
<b>diff:</b> <i>{{ mdrender(object.project, values.1) }}</i>
<b>diff:</b> <i>{{ mdrender(project, values.1) }}</i>
</dd>
{# CONTENT #}
{% elif field_name in ["content_diff"] %}
<dd style="background: #eee; padding: 5px 15px; color: #444">
<b>diff:</b> <i>{{ mdrender(object.project, values.1) }}</i>
<b>diff:</b> <i>{{ mdrender(project, values.1) }}</i>
</dd>
{# ASSIGNED TO #}
{% elif field_name == "assigned_to" %}

View File

@ -30,7 +30,7 @@ from taiga.base import tags
from taiga.users.models import User
from taiga.projects.notifications import WatchedResourceMixin
from taiga.projects.notifications.mixins import WatchedResourceMixin
from taiga.projects.occ import OCCResourceMixin
from taiga.projects.history.mixins import HistoryResourceMixin

View File

@ -22,7 +22,7 @@ from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _
from taiga.projects.occ import OCCModelMixin
from taiga.projects.notifications import WatchedModelMixin
from taiga.projects.notifications.mixins import WatchedModelMixin
from taiga.projects.mixins.blocked import BlockedMixin
from taiga.base.tags import TaggedMixin

View File

@ -25,7 +25,7 @@ from taiga.base import exceptions as exc
from taiga.base.decorators import detail_route
from taiga.base.api import ModelCrudViewSet
from taiga.projects.notifications import WatchedResourceMixin
from taiga.projects.notifications.mixins import WatchedResourceMixin
from taiga.projects.history.mixins import HistoryResourceMixin

View File

@ -22,7 +22,7 @@ from django.core.exceptions import ValidationError
from taiga.base.utils.slug import slugify_uniquely
from taiga.base.utils.dicts import dict_sum
from taiga.projects.notifications import WatchedModelMixin
from taiga.projects.notifications.mixins import WatchedModelMixin
from taiga.projects.userstories.models import UserStory
import itertools

View File

@ -1,20 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from .mixins import WatchedResourceMixin
from .mixins import WatchedModelMixin
__all__ = ["WatchedModelMixin", "WatchedResourceMixin"]

View File

@ -0,0 +1,29 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.core.management.base import BaseCommand
from taiga.base.utils.iterators import iter_queryset
from taiga.projects.notifications.models import HistoryChangeNotification
from taiga.projects.notifications.services import send_sync_notifications
class Command(BaseCommand):
def handle(self, *args, **options):
qs = HistoryChangeNotification.objects.all()
for change_notification in iter_queryset(qs, itersize=100):
send_sync_notifications(change_notification.pk)

View File

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('projects', '0005_membership_invitation_extra_text'),
('history', '0004_historyentry_is_hidden'),
('notifications', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='HistoryChangeNotification',
fields=[
('id', models.AutoField(serialize=False, primary_key=True, verbose_name='ID', auto_created=True)),
('key', models.CharField(max_length=255, editable=False)),
('created_datetime', models.DateTimeField(verbose_name='created date time', auto_now_add=True)),
('updated_datetime', models.DateTimeField(verbose_name='updated date time', auto_now_add=True)),
('history_type', models.SmallIntegerField(choices=[(1, 'Change'), (2, 'Create'), (3, 'Delete')])),
('history_entries', models.ManyToManyField(blank=True, null=True, to='history.HistoryEntry', verbose_name='history entries', related_name='+')),
('notify_users', models.ManyToManyField(blank=True, null=True, to=settings.AUTH_USER_MODEL, verbose_name='notify users', related_name='+')),
('owner', models.ForeignKey(related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='owner')),
('project', models.ForeignKey(related_name='+', to='projects.Project', verbose_name='project')),
],
options={
},
bases=(models.Model,),
),
]

View File

@ -62,8 +62,7 @@ class WatchedResourceMixin(object):
# Get a complete list of notifiable users for current
# object and send the change notification to them.
users = services.get_users_to_notify(obj, history=history)
services.send_notifications(obj, history=history, users=users)
services.send_notifications(obj, history=history)
def post_save(self, obj, created=False):
self.send_notifications(obj)

View File

@ -19,7 +19,7 @@ from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from .choices import NOTIFY_LEVEL_CHOICES
from taiga.projects.history.choices import HISTORY_TYPE_CHOICES
class NotifyPolicy(models.Model):
"""
@ -43,3 +43,27 @@ class NotifyPolicy(models.Model):
self.modified_at = timezone.now()
return super().save(*args, **kwargs)
class HistoryChangeNotification(models.Model):
"""
This class controls the pending notifications for an object, it should be instantiated
or updated when an object requires notifications.
"""
key = models.CharField(max_length=255, unique=False, editable=False)
owner = models.ForeignKey("users.User", null=False, blank=False,
verbose_name="owner",related_name="+")
created_datetime = models.DateTimeField(null=False, blank=False, auto_now_add=True,
verbose_name=_("created date time"))
updated_datetime = models.DateTimeField(null=False, blank=False, auto_now_add=True,
verbose_name=_("updated date time"))
history_entries = models.ManyToManyField("history.HistoryEntry", null=True, blank=True,
verbose_name="history entries",
related_name="+")
notify_users = models.ManyToManyField("users.User", null=True, blank=True,
verbose_name="notify users",
related_name="+")
project = models.ForeignKey("projects.Project", null=False, blank=False,
verbose_name="project",related_name="+")
history_type = models.SmallIntegerField(choices=HISTORY_TYPE_CHOICES)

View File

@ -19,6 +19,9 @@ from functools import partial
from django.apps import apps
from django.db import IntegrityError
from django.contrib.contenttypes.models import ContentType
from django.utils import timezone
from django.db import transaction
from django.conf import settings
from djmail import template_mail
@ -26,7 +29,12 @@ from taiga.base import exceptions as exc
from taiga.base.utils.text import strip_lines
from taiga.projects.notifications.choices import NotifyLevel
from taiga.projects.history.choices import HistoryType
from taiga.projects.history.services import (make_key_from_model_object,
get_last_snapshot_for_key,
get_model_from_key)
from taiga.users.models import User
from .models import HistoryChangeNotification
def notify_policy_exists(project, user) -> bool:
"""
@ -113,7 +121,7 @@ def analize_object_for_watchers(obj:object, history:object):
obj.watchers.add(user)
def get_users_to_notify(obj, *, history) -> list:
def get_users_to_notify(obj, *, discard_users=None) -> list:
"""
Get filtered set of users to notify for specified
model instance and changer.
@ -138,18 +146,18 @@ def get_users_to_notify(obj, *, history) -> list:
candidates.update(filter(_can_notify_light, obj.get_participants()))
# Remove the changer from candidates
candidates.discard(history.owner)
if discard_users:
candidates = candidates - set(discard_users)
return frozenset(candidates)
def _resolve_template_name(obj, *, change_type:int) -> str:
def _resolve_template_name(model:object, *, change_type:int) -> str:
"""
Ginven an changed model instance and change type,
return the preformated template name for it.
"""
ct = ContentType.objects.get_for_model(obj.__class__)
ct = ContentType.objects.get_for_model(model)
# Resolve integer enum value from "change_type"
# parameter to human readable string
if change_type == HistoryType.create:
@ -158,7 +166,6 @@ def _resolve_template_name(obj, *, change_type:int) -> str:
change_type = "change"
else:
change_type = "delete"
tmpl = "{app_label}/{model}-{change}"
return tmpl.format(app_label=ct.app_label,
model=ct.model,
@ -178,19 +185,63 @@ def _make_template_mail(name:str):
return cls()
def send_notifications(obj, *, history, users):
@transaction.atomic
def send_notifications(obj, *, history):
key = make_key_from_model_object(obj)
owner = User.objects.get(pk=history.user["pk"])
notification, created = (HistoryChangeNotification.objects.select_for_update()
.get_or_create(key=key,
owner=owner,
project=obj.project,
history_type = history.type))
notification.updated_datetime = timezone.now()
notification.save()
notification.history_entries.add(history)
# Get a complete list of notifiable users for current
# object and send the change notification to them.
notify_users = get_users_to_notify(obj, discard_users=[notification.owner])
for notify_user in notify_users:
notification.notify_users.add(notify_user)
# If we are the min interval is 0 it just work in a synchronous and spamming way
if settings.CHANGE_NOTIFICATIONS_MIN_INTERVAL == 0:
send_sync_notifications(notification.id)
@transaction.atomic
def send_sync_notifications(notification_id):
"""
Given changed instance, history entry and
Given changed instance, calculate the history entry and
a complete list for users to notify, send
email to all users.
"""
context = {"object": obj,
"changer": history.owner,
"comment": history.comment,
"changed_fields": history.values_diff}
template_name = _resolve_template_name(obj, change_type=history.type)
notification = HistoryChangeNotification.objects.select_for_update().get(pk=notification_id)
# If the las modification is too recent we ignore it
now = timezone.now()
time_diff = now - notification.updated_datetime
if time_diff.seconds < settings.CHANGE_NOTIFICATIONS_MIN_INTERVAL:
return
history_entries = tuple(notification.history_entries.all().order_by("created_at"))
obj, _ = get_last_snapshot_for_key(notification.key)
context = {"snapshot": obj.snapshot,
"project": notification.project,
"changer": notification.owner,
"history_entries": history_entries}
model = get_model_from_key(notification.key)
template_name = _resolve_template_name(model, change_type=notification.history_type)
email = _make_template_mail(template_name)
for user in users:
for user in notification.notify_users.distinct():
email.send(user.email, context)
notification.delete()
def process_sync_notifications():
for notification in HistoryChangeNotification.objects.all():
send_sync_notifications(notification.pk)

View File

@ -1,22 +1,24 @@
{% extends "emails/base.jinja" %}
{% set final_url = resolve_front_url("issue", object.project.slug, object.ref) %}
{% set final_url_name = "Taiga - View issue #{0}".format(object.ref) %}
{% set final_url = resolve_front_url("issue", project.slug, snapshot.ref) %}
{% set final_url_name = "Taiga - View issue #{0}".format(snapshot.ref) %}
{% block body %}
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
<tr>
<td>
<h1>Project: {{ object.project.name }}</h1>
<h2>Issue #{{ object.ref }}: {{ object.subject }}</h2>
<h1>Project: {{ project.name }}</h1>
<h2>Issue #{{ snapshot.ref }}: {{ snapshot.subject }}</h2>
<p>Updated by <b>{{ changer.get_full_name() }}</b>.</p>
{% if comment %}
<p>Comment <b>{{ mdrender(object.project, comment) }}</b></p>
{% for entry in history_entries%}
{% if entry.comment %}
<p>Comment <b>{{ mdrender(project, entry.comment) }}</b></p>
{% endif %}
{% set changed_fields = entry.values_diff %}
{% if changed_fields %}
<p>Updated fields:</p>
{% include "emails/includes/fields_diff-html.jinja" %}
{% endif %}
{% endfor %}
</td>
</tr>
</table>

View File

@ -1,15 +1,17 @@
{% set final_url = resolve_front_url("issue", object.project.slug, object.ref) %}
{% set final_url_name = "Taiga - View issue #{0}".format(object.ref) %}
{% set final_url = resolve_front_url("issue", project.slug, snapshot.ref) %}
{% set final_url_name = "Taiga - View issue #{0}".format(snapshot.ref) %}
- Project: {{ object.project.name }}
- Issue #{{ object.ref }}: {{ object.subject }}
- Project: {{ project.name }}
- Issue #{{ snapshot.ref }}: {{ snapshot.subject }}
- Updated by {{ changer.get_full_name() }}
{% if comment %}
Comment: {{ comment|linebreaksbr }}
{% for entry in history_entries%}
{% if entry.comment %}
Comment: {{ entry.comment|linebreaksbr }}
{% endif %}
{% set changed_fields = entry.values_diff %}
{% if changed_fields %}
- Updated fields:
{% include "emails/includes/fields_diff-text.jinja" %}
{% endif %}
{% endfor %}
** More info at {{ final_url_name }} ({{ final_url }}) **

View File

@ -1 +1 @@
[{{ object.project.name|safe }}] Updated the issue #{{ object.ref|safe }} "{{ object.subject|safe }}"
[{{ project.name|safe }}] Updated the issue #{{ snapshot.ref|safe }} "{{ snapshot.subject|safe }}"

View File

@ -1,14 +1,14 @@
{% extends "emails/base.jinja" %}
{% set final_url = resolve_front_url("issue", object.project.slug, object.ref) %}
{% set final_url_name = "Taiga - View issue #{0}".format(object.ref) %}
{% set final_url = resolve_front_url("issue", project.slug, snapshot.ref) %}
{% set final_url_name = "Taiga - View issue #{0}".format(snapshot.ref) %}
{% block body %}
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
<tr>
<td>
<h1>Project: {{ object.project.name }}</h1>
<h2>Issue #{{ object.ref }}: {{ object.subject }}</h2>
<h1>Project: {{ project.name }}</h1>
<h2>Issue #{{ snapshot.ref }}: {{ snapshot.subject }}</h2>
<p>Created by <b>{{ changer.get_full_name() }}</b>.</p>
</td>
</tr>

View File

@ -1,8 +1,8 @@
{% set final_url = resolve_front_url("issue", object.project.slug, object.ref) %}
{% set final_url_name = "Taiga - View issue #{0}".format(object.ref) %}
{% set final_url = resolve_front_url("issue", project.slug, snapshot.ref) %}
{% set final_url_name = "Taiga - View issue #{0}".format(snapshot.ref) %}
- Project: {{ object.project.name }}
- US #{{ object.ref }}: {{ object.subject }}
- Project: {{ project.name }}
- US #{{ snapshot.ref }}: {{ snapshot.subject }}
- Created by {{ changer.get_full_name() }}
** More info at {{ final_url_name }} ({{ final_url }}) **

View File

@ -1 +1 @@
[{{ object.project.name|safe }}] Created the issue #{{ object.ref|safe }} "{{ object.subject|safe }}"
[{{ project.name|safe }}] Created the issue #{{ snapshot.ref|safe }} "{{ snapshot.subject|safe }}"

View File

@ -4,8 +4,8 @@
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
<tr>
<td>
<h1>{{ object.project.name }}</h1>
<h2>Issue #{{ object.ref }}: {{ object.subject }}</h2>
<h1>{{ project.name }}</h1>
<h2>Issue #{{ snapshot.ref }}: {{ snapshot.subject }}</h2>
<p>Deleted by <b>{{ changer.get_full_name() }}</b></p>
</td>
</tr>

View File

@ -1,3 +1,3 @@
- Project: {{ object.project.name }}
- Issue #{{ object.ref }}: {{ object.subject }}
- Project: {{ project.name }}
- Issue #{{ snapshot.ref }}: {{ snapshot.subject }}
- Deleted by {{ changer.get_full_name() }}

View File

@ -1 +1 @@
[{{ object.project.name|safe }}] Deleted the issue #{{ object.ref|safe }} "{{ object.subject|safe }}"
[{{ project.name|safe }}] Deleted the issue #{{ snapshot.ref|safe }} "{{ snapshot.subject|safe }}"

View File

@ -1,22 +1,24 @@
{% extends "emails/base.jinja" %}
{% set final_url = resolve_front_url("taskboard", object.project.slug, object.slug) %}
{% set final_url_name = "Taiga - View milestone #{0}".format(object.slug) %}
{% set final_url = resolve_front_url("taskboard", project.slug, snapshot.slug) %}
{% set final_url_name = "Taiga - View milestone #{0}".format(snapshot.slug) %}
{% block body %}
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
<tr>
<td>
<h1>Project: {{ object.project.name }}</h1>
<h2>Milestone #{{ object.slug }}: {{ object.name }}</h2>
<h1>Project: {{ project.name }}</h1>
<h2>Milestone #{{ snapshot.slug }}: {{ snapshot.name }}</h2>
<p>Updated by <b>{{ changer.get_full_name() }}</b>.</p>
{% if comment %}
<p>Comment <b>{{ comment|linebreaksbr }}</b></p>
{% for entry in history_entries%}
{% if entry.comment %}
<p>Comment <b>{{ entry.comment|linebreaksbr }}</b></p>
{% endif %}
{% set changed_fields = entry.values_diff %}
{% if changed_fields %}
<p>Updated fields:</p>
{% include "emails/includes/fields_diff-html.jinja" %}
{% endif %}
{% endfor %}
</td>
</tr>
</table>

View File

@ -1,15 +1,17 @@
{% set final_url = resolve_front_url("taskboard", object.project.slug, object.slug) %}
{% set final_url_name = "Taiga - View milestone #{0}".format(object.slug) %}
{% set final_url = resolve_front_url("taskboard", project.slug, snapshot.slug) %}
{% set final_url_name = "Taiga - View milestone #{0}".format(snapshot.slug) %}
- Project: {{ object.project.name }}
- Milestone #{{ object.slug }}: {{ object.name }}
- Project: {{ project.name }}
- Milestone #{{ snapshot.slug }}: {{ snapshot.name }}
- Updated by {{ changer.get_full_name() }}
{% if comment %}
Comment: {{ comment|linebreaksbr }}
{% for entry in history_entries%}
{% if entry.comment %}
Comment: {{ entry.comment|linebreaksbr }}
{% endif %}
{% set changed_fields = entry.values_diff %}
{% if changed_fields %}
- Updated fields:
{% include "emails/includes/fields_diff-text.jinja" %}
{% endif %}
{% endfor %}
** More info at {{ final_url_name }} ({{ final_url }}) **

View File

@ -1 +1 @@
[{{ object.project.name|safe }}] Updated the milestone #{{ object.slug|safe }} "{{ object.name|safe }}"
[{{ project.name|safe }}] Updated the milestone #{{ snapshot.slug|safe }} "{{ snapshot.name|safe }}"

View File

@ -1,14 +1,14 @@
{% extends "emails/base.jinja" %}
{% set final_url = resolve_front_url("taskboard", object.project.slug, object.slug) %}
{% set final_url_name = "Taiga - View milestone #{0}".format(object.slug) %}
{% set final_url = resolve_front_url("taskboard", project.slug, snapshot.slug) %}
{% set final_url_name = "Taiga - View milestone #{0}".format(snapshot.slug) %}
{% block body %}
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
<tr>
<td>
<h1>Project: {{ object.project.name }}</h1>
<h2>Milestone #{{ object.slug }}: {{ object.name }}</h2>
<h1>Project: {{ project.name }}</h1>
<h2>Milestone #{{ snapshot.slug }}: {{ snapshot.name }}</h2>
<p>Created by <b>{{ changer.get_full_name() }}</b>.</p>
</td>
</tr>

View File

@ -1,8 +1,8 @@
{% set final_url = resolve_front_url("taskboard", object.project.slug, object.slug) %}
{% set final_url_name = "Taiga - View milestone #{0}".format(object.slug) %}
{% set final_url = resolve_front_url("taskboard", project.slug, snapshot.slug) %}
{% set final_url_name = "Taiga - View milestone #{0}".format(snapshot.slug) %}
- Project: {{ object.project.name }}
- Milestone #{{ object.slug }}: {{ object.name }}
- Project: {{ project.name }}
- Milestone #{{ snapshot.slug }}: {{ snapshot.name }}
- Created by {{ changer.get_full_name() }}
** More info at {{ final_url_name }} ({{ final_url }}) **

View File

@ -1 +1 @@
[{{ object.project.name|safe }}] Created the milestone #{{ object.slug|safe }} "{{ object.name|safe }}"
[{{ project.name|safe }}] Created the milestone #{{ snapshot.slug|safe }} "{{ snapshot.name|safe }}"

View File

@ -4,8 +4,8 @@
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
<tr>
<td>
<h1>{{ object.project.name }}</h1>
<h2>Milestone #{{ object.slug }}: {{ object.name }}</h2>
<h1>{{ project.name }}</h1>
<h2>Milestone #{{ snapshot.slug }}: {{ snapshot.name }}</h2>
<p>Deleted by <b>{{ changer.get_full_name() }}</b></p>
</td>
</tr>

View File

@ -1,3 +1,3 @@
- Project: {{ object.project.name }}
- Milestone #{{ object.slug }}: {{ object.name }}
- Project: {{ project.name }}
- Milestone #{{ snapshot.slug }}: {{ snapshot.name }}
- Deleted by {{ changer.get_full_name() }}

View File

@ -1 +1 @@
[{{ object.project.name|safe }}] Deleted the milestone #{{ object.slug|safe }} "{{ object.name|safe }}"
[{{ project.name|safe }}] Deleted the milestone #{{ snapshot.slug|safe }} "{{ snapshot.name|safe }}"

View File

@ -1,21 +1,23 @@
{% extends "emails/base.jinja" %}
{% set final_url = resolve_front_url("project-admin", object.slug) %}
{% set final_url_name = "Taiga - View Project #{0}".format(object.slug) %}
{% set final_url = resolve_front_url("project-admin", snapshot.slug) %}
{% set final_url_name = "Taiga - View Project #{0}".format(snapshot.slug) %}
{% block body %}
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
<tr>
<td>
<h2>Project #{{ object.slug }}: {{ object.name }}</h2>
<h2>Project #{{ snapshot.slug }}: {{ snapshot.name }}</h2>
<p>Updated by <b>{{ changer.get_full_name() }}</b>.</p>
{% if comment %}
<p>Comment <b>{{ comment|linebreaksbr }}</b></p>
{% for entry in history_entries%}
{% if entry.comment %}
<p>Comment <b>{{ entry.comment|linebreaksbr }}</b></p>
{% endif %}
{% set changed_fields = entry.values_diff %}
{% if changed_fields %}
<p>Updated fields:</p>
{% include "emails/includes/fields_diff-html.jinja" %}
{% endif %}
{% endfor %}
</td>
</tr>
</table>

View File

@ -1,16 +1,17 @@
{% set final_url = resolve_front_url("project-admin", object.slug) %}
{% set final_url_name = "Taiga - View Project #{0}".format(object.slug) %}
{% set final_url = resolve_front_url("project-admin", snapshot.slug) %}
{% set final_url_name = "Taiga - View Project #{0}".format(snapshot.slug) %}
- Project #{{ object.slug }}: {{ object.name }}
- Project #{{ snapshot.slug }}: {{ snapshot.name }}
- Updated by {{ changer.get_full_name() }}
{% if comment %}
Comment: {{ comment|linebreaksbr }}
{% for entry in history_entries%}
{% if entry.comment %}
Comment: {{ entry.comment|linebreaksbr }}
{% endif %}
{% if changed_fields %}
- Updated fields:
{% set changed_fields = entry.values_diff %}
{% for field_name, values in changed_fields.items() %}
* {{ verbose_name(object, field_name) }}</b>: from '{{ values.0 }}' to '{{ values.1 }}'.
{% endfor %}
{% endif %}
{% endfor %}
** More info at {{ final_url_name }} ({{ final_url }}) **

View File

@ -1 +1 @@
[{{ object.name|safe }}] Updated the project #{{ object.slug|safe }}
[{{ snapshot.name|safe }}] Updated the project #{{ snapshot.slug|safe }}

View File

@ -1,12 +1,12 @@
{% set final_url = resolve_front_url("project-admin", object.slug) %}
{% set final_url_name = "Taiga - View Project #{0}".format(object.slug) %}
{% set final_url = resolve_front_url("project-admin", snapshot.slug) %}
{% set final_url_name = "Taiga - View Project #{0}".format(snapshot.slug) %}
{% block body %}
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
<tr>
<td>
<h1>Project: {{ object.project.name }}</h1>
<h2>Project #{{ object.slug }}: {{ object.name }}</h2>
<h1>Project: {{ project.name }}</h1>
<h2>Project #{{ snapshot.slug }}: {{ snapshot.name }}</h2>
<p>Created by <b>{{ changer.get_full_name() }}</b>.</p>
</td>
</tr>

View File

@ -1,7 +1,7 @@
{% set final_url = resolve_front_url("project-admin", object.slug) %}
{% set final_url_name = "Taiga - View Project #{0}".format(object.slug) %}
{% set final_url = resolve_front_url("project-admin", snapshot.slug) %}
{% set final_url_name = "Taiga - View Project #{0}".format(snapshot.slug) %}
- Project #{{ object.slug }}: {{ object.name }}
- Project #{{ snapshot.slug }}: {{ snapshot.name }}
- Created by {{ changer.get_full_name() }}
** More info at {{ final_url_name }} ({{ final_url }}) **

View File

@ -1 +1 @@
[{{ object.name|safe }}] Created the project #{{ object.slug|safe }}
[{{ snapshot.name|safe }}] Created the project #{{ snapshot.slug|safe }}

View File

@ -4,7 +4,7 @@
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
<tr>
<td>
<h2>Project #{{ object.slug }}: {{ object.name }}</h2>
<h2>Project #{{ snapshot.slug }}: {{ snapshot.name }}</h2>
<p>Deleted by <b>{{ changer.get_full_name() }}</b></p>
</td>
</tr>

View File

@ -1,2 +1,2 @@
- Project #{{ object.slug }}: {{ object.name }}
- Project #{{ snapshot.slug }}: {{ snapshot.name }}
- Deleted by {{ changer.get_full_name() }}

View File

@ -1 +1 @@
[{{ object.name|safe }}] Deleted the project #{{ object.slug|safe }}
[{{ snapshot.name|safe }}] Deleted the project #{{ snapshot.slug|safe }}

View File

@ -1,22 +1,24 @@
{% extends "emails/base.jinja" %}
{% set final_url = resolve_front_url("task", object.project.slug, object.ref) %}
{% set final_url_name = "Taiga - View task #{0}".format(object.ref) %}
{% set final_url = resolve_front_url("task", project.slug, snapshot.ref) %}
{% set final_url_name = "Taiga - View task #{0}".format(snapshot.ref) %}
{% block body %}
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
<tr>
<td>
<h1>Project: {{ object.project.name }}</h1>
<h2>Task #{{ object.ref }}: {{ object.subject }}</h2>
<h1>Project: {{ project.name }}</h1>
<h2>Task #{{ snapshot.ref }}: {{ snapshot.subject }}</h2>
<p>Updated by <b>{{ changer.get_full_name() }}</b>.</p>
{% if comment %}
<p>Comment <b>{{ mdrender(object.project, comment) }}</b></p>
{% for entry in history_entries%}
{% if entry.comment %}
<p>Comment <b>{{ mdrender(project, entry.comment) }}</b></p>
{% endif %}
{% set changed_fields = entry.values_diff %}
{% if changed_fields %}
<p>Updated fields:</p>
{% include "emails/includes/fields_diff-html.jinja" %}
{% endif %}
{% endfor %}
</td>
</tr>
</table>

View File

@ -1,15 +1,17 @@
{% set final_url = resolve_front_url("task", object.project.slug, object.ref) %}
{% set final_url_name = "Taiga - View task #{0}".format(object.ref) %}
{% set final_url = resolve_front_url("task", project.slug, snapshot.ref) %}
{% set final_url_name = "Taiga - View task #{0}".format(snapshot.ref) %}
- Project: {{ object.project.name }}
- Task #{{ object.ref }}: {{ object.subject }}
- Project: {{ project.name }}
- Task #{{ snapshot.ref }}: {{ snapshot.subject }}
- Updated by {{ changer.get_full_name() }}
{% if comment %}
Comment: {{ comment|linebreaksbr }}
{% for entry in history_entries%}
{% if entry.comment %}
Comment: {{ entry.comment|linebreaksbr }}
{% endif %}
{% set changed_fields = entry.values_diff %}
{% if changed_fields %}
- Updated fields:
{% include "emails/includes/fields_diff-text.jinja" %}
{% endif %}
{% endfor %}
** More info at {{ final_url_name }} ({{ final_url }}) **

View File

@ -1 +1 @@
[{{ object.project.name|safe }}] Updated the task #{{ object.ref }} "{{ object.subject|safe }}"
[{{ project.name|safe }}] Updated the task #{{ snapshot.ref }} "{{ snapshot.subject|safe }}"

View File

@ -1,14 +1,14 @@
{% extends "emails/base.jinja" %}
{% set final_url = resolve_front_url("task", object.project.slug, object.ref) %}
{% set final_url_name = "Taiga - View task #{0}".format(object.ref) %}
{% set final_url = resolve_front_url("task", project.slug, snapshot.ref) %}
{% set final_url_name = "Taiga - View task #{0}".format(snapshot.ref) %}
{% block body %}
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
<tr>
<td>
<h1>Project: {{ object.project.name }}</h1>
<h2>Task #{{ object.ref }}: {{ object.subject }}</h2>
<h1>Project: {{ project.name }}</h1>
<h2>Task #{{ snapshot.ref }}: {{ snapshot.subject }}</h2>
<p>Created by <b>{{ changer.get_full_name() }}</b>.</p>
</td>
</tr>

View File

@ -1,8 +1,8 @@
{% set final_url = resolve_front_url("task", object.project.slug, object.ref) %}
{% set final_url_name = "Taiga - View task #{0}".format(object.ref) %}
{% set final_url = resolve_front_url("task", project.slug, snapshot.ref) %}
{% set final_url_name = "Taiga - View task #{0}".format(snapshot.ref) %}
- Project: {{ object.project.name }}
- Task #{{ object.ref }}: {{ object.subject }}
- Project: {{ project.name }}
- Task #{{ snapshot.ref }}: {{ snapshot.subject }}
- Created by {{ changer.get_full_name() }}
** More info at {{ final_url_name }} ({{ final_url }}) **

View File

@ -1 +1 @@
[{{ object.project.name|safe }}] Created the task #{{ object.ref }} "{{ object.subject|safe }}"
[{{ project.name|safe }}] Created the task #{{ snapshot.ref }} "{{ snapshot.subject|safe }}"

View File

@ -4,8 +4,8 @@
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
<tr>
<td>
<h1>{{ object.project.name }}</h1>
<h2>Task #{{ object.ref }}: {{ object.subject }}</h2>
<h1>{{ project.name }}</h1>
<h2>Task #{{ snapshot.ref }}: {{ snapshot.subject }}</h2>
<p>Deleted by <b>{{ changer.get_full_name() }}</b></p>
</td>
</tr>

View File

@ -1,4 +1,4 @@
- Project: {{ object.project.name }}
- Task #{{ object.ref }}: {{ object.subject }}
- Project: {{ project.name }}
- Task #{{ snapshot.ref }}: {{ snapshot.subject }}
- Deleted by {{ changer.get_full_name() }}

View File

@ -1 +1 @@
[{{ object.project.name|safe }}] Deleted the task #{{ object.ref }} "{{ object.subject|safe }}"
[{{ project.name|safe }}] Deleted the task #{{ snapshot.ref }} "{{ snapshot.subject|safe }}"

View File

@ -1,22 +1,24 @@
{% extends "emails/base.jinja" %}
{% set final_url = resolve_front_url("userstory", object.project.slug, object.ref) %}
{% set final_url_name = "Taiga - View US #{0}".format(object.ref) %}
{% set final_url = resolve_front_url("userstory", project.slug, snapshot.ref) %}
{% set final_url_name = "Taiga - View US #{0}".format(snapshot.ref) %}
{% block body %}
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
<tr>
<td>
<h1>Project: {{ object.project.name }}</h1>
<h2>US #{{ object.ref }}: {{ object.subject }}</h2>
<h1>Project: {{ project.name }}</h1>
<h2>US #{{ snapshot.ref }}: {{ snapshot.subject }}</h2>
<p>Updated by <b>{{ changer.get_full_name() }}</b>.</p>
{% if comment %}
<p>Comment <b>{{ mdrender(object.project, comment)}}</b></p>
{% for entry in history_entries%}
{% if entry.comment %}
<p>Comment <b>{{ mdrender(project, entry.comment) }}</b></p>
{% endif %}
{% set changed_fields = entry.values_diff %}
{% if changed_fields %}
<p>Updated fields:</p>
{% include "emails/includes/fields_diff-html.jinja" %}
{% endif %}
{% endfor %}
</td>
</tr>
</table>

View File

@ -1,15 +1,17 @@
{% set final_url = resolve_front_url("userstory", object.project.slug, object.ref) %}
{% set final_url_name = "Taiga - View US #{0}".format(object.ref) %}
{% set final_url = resolve_front_url("userstory", project.slug, snapshot.ref) %}
{% set final_url_name = "Taiga - View US #{0}".format(snapshot.ref) %}
- Project: {{ object.project.name }}
- US #{{ object.ref }}: {{ object.subject }}
- Project: {{ project.name }}
- US #{{ snapshot.ref }}: {{ snapshot.subject }}
- Updated by {{ changer.get_full_name() }}
{% if comment %}
Comment: {{ comment|linebreaksbr }}
{% for entry in history_entries%}
{% if entry.comment %}
Comment: {{ entry.comment|linebreaksbr }}
{% endif %}
{% set changed_fields = entry.values_diff %}
{% if changed_fields %}
- Updated fields:
{% include "emails/includes/fields_diff-text.jinja" %}
{% endif %}
{% endfor %}
** More info at {{ final_url_name }} ({{ final_url }}) **

View File

@ -1 +1 @@
[{{ object.project.name|safe }}] Updated the US #{{ object.ref }} "{{ object.subject|safe }}"
[{{ project.name|safe }}] Updated the US #{{ snapshot.ref }} "{{ snapshot.subject|safe }}"

View File

@ -1,14 +1,14 @@
{% extends "emails/base.jinja" %}
{% set final_url = resolve_front_url("userstory", object.project.slug, object.ref) %}
{% set final_url_name = "Taiga - View US #{0}".format(object.ref) %}
{% set final_url = resolve_front_url("userstory", project.slug, snapshot.ref) %}
{% set final_url_name = "Taiga - View US #{0}".format(snapshot.ref) %}
{% block body %}
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
<tr>
<td>
<h1>Project: {{ object.project.name }}</h1>
<h2>US #{{ object.ref }}: {{ object.subject }}</h2>
<h1>Project: {{ project.name }}</h1>
<h2>US #{{ snapshot.ref }}: {{ snapshot.subject }}</h2>
<p>Created by <b>{{ changer.get_full_name() }}</b>.</p>
</td>
</tr>

View File

@ -1,8 +1,8 @@
{% set final_url = resolve_front_url("userstory", object.project.slug, object.ref) %}
{% set final_url_name = "Taiga - View US #{0}".format(object.ref) %}
{% set final_url = resolve_front_url("userstory", project.slug, snapshot.ref) %}
{% set final_url_name = "Taiga - View US #{0}".format(snapshot.ref) %}
- Project: {{ object.project.name }}
- US #{{ object.ref }}: {{ object.subject }}
- Project: {{ project.name }}
- US #{{ snapshot.ref }}: {{ snapshot.subject }}
- Created by {{ changer.get_full_name() }}
** More info at {{ final_url_name }} ({{ final_url }}) **

View File

@ -1 +1 @@
[{{ object.project.name|safe }}] Created the US #{{ object.ref }} "{{ object.subject|safe }}"
[{{ project.name|safe }}] Created the US #{{ snapshot.ref }} "{{ snapshot.subject|safe }}"

View File

@ -4,8 +4,8 @@
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
<tr>
<td>
<h1>{{ object.project.name }}</h1>
<h2>US #{{ object.ref }}: {{ object.subject }}</h2>
<h1>{{ project.name }}</h1>
<h2>US #{{ snapshot.ref }}: {{ snapshot.subject }}</h2>
<p>Deleted by <b>{{ changer.get_full_name() }}</b></p>
</td>
</tr>

View File

@ -1,3 +1,3 @@
- Project: {{ object.project.name }}
- US #{{ object.ref }}: {{ object.subject }}
- Project: {{ project.name }}
- US #{{ snapshot.ref }}: {{ snapshot.subject }}
- Deleted by {{ changer.get_full_name() }}

View File

@ -1 +1 @@
[{{ object.project.name|safe }}] Deleted the US #{{ object.ref }} "{{ object.subject|safe }}"
[{{ project.name|safe }}] Deleted the US #{{ snapshot.ref }} "{{ snapshot.subject|safe }}"

View File

@ -1,22 +1,24 @@
{% extends "emails/base.jinja" %}
{% set final_url = resolve_front_url("wiki", object.project.slug, object.slug) %}
{% set final_url_name = "Taiga - View Wiki Page '{0}'".format(object.slug) %}
{% set final_url = resolve_front_url("wiki", project.slug, snapshot.slug) %}
{% set final_url_name = "Taiga - View Wiki Page '{0}'".format(snapshot.slug) %}
{% block body %}
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
<tr>
<td>
<h1>Project: {{ object.project.name }}</h1>
<h2>Wiki Page: {{ object.slug }}</h2>
<h1>Project: {{ project.name }}</h1>
<h2>Wiki Page: {{ snapshot.slug }}</h2>
<p>Updated by <b>{{ changer.get_full_name() }}</b>.</p>
{% if comment %}
<p>Comment <b>{{ mdrender(object.project, comment) }}</b></p>
{% for entry in history_entries%}
{% if entry.comment %}
<p>Comment <b>{{ mdrender(project, entry.comment) }}</b></p>
{% endif %}
{% set changed_fields = entry.values_diff %}
{% if changed_fields %}
<p>Updated fields:</p>
{% include "emails/includes/fields_diff-html.jinja" %}
{% endif %}
{% endfor %}
</td>
</tr>
</table>

View File

@ -1,15 +1,17 @@
{% set final_url = resolve_front_url("wiki", object.project.slug, object.slug) %}
{% set final_url_name = "Taiga - View Wiki Page '{0}'".format(object.slug) %}
{% set final_url = resolve_front_url("wiki", project.slug, snapshot.slug) %}
{% set final_url_name = "Taiga - View Wiki Page '{0}'".format(snapshot.slug) %}
- Project: {{ object.project.name }}
- Wiki Page: {{ object.slug }}
- Project: {{ project.name }}
- Wiki Page: {{ snapshot.slug }}
- Updated by {{ changer.get_full_name() }}
{% if comment %}
Comment: {{ comment|linebreaksbr }}
{% for entry in history_entries%}
{% if entry.comment %}
Comment: {{ entry.comment|linebreaksbr }}
{% endif %}
{% set changed_fields = entry.values_diff %}
{% if changed_fields %}
- Updated fields:
{% include "emails/includes/fields_diff-text.jinja" %}
{% endif %}
{% endfor %}
** More info at {{ final_url_name }} ({{ final_url }}) **

View File

@ -1 +1 @@
[{{ object.project.name|safe }}] Updated the Wiki Page "{{ object.slug }}"
[{{ project.name|safe }}] Updated the Wiki Page "{{ snapshot.slug }}"

View File

@ -1,14 +1,14 @@
{% extends "emails/base.jinja" %}
{% set final_url = resolve_front_url("wiki", object.project.slug, object.slug) %}
{% set final_url_name = "Taiga - View Wiki Page '{0}'".format(object.slug) %}
{% set final_url = resolve_front_url("wiki", project.slug, snapshot.slug) %}
{% set final_url_name = "Taiga - View Wiki Page '{0}'".format(snapshot.slug) %}
{% block body %}
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
<tr>
<td>
<h1>Project: {{ object.project.name }}</h1>
<h2>Wiki Page: {{ object.slug }}</h2>
<h1>Project: {{ project.name }}</h1>
<h2>Wiki Page: {{ snapshot.slug }}</h2>
<p>Created by <b>{{ changer.get_full_name() }}</b>.</p>
</td>
</tr>

View File

@ -1,8 +1,8 @@
{% set final_url = resolve_front_url("wiki", object.project.slug, object.slug) %}
{% set final_url_name = "Taiga - View Wiki Page '{0}'".format(object.slug) %}
{% set final_url = resolve_front_url("wiki", project.slug, snapshot.slug) %}
{% set final_url_name = "Taiga - View Wiki Page '{0}'".format(snapshot.slug) %}
- Project: {{ object.project.name }}
- Wiki Page: {{ object.slug }}
- Project: {{ project.name }}
- Wiki Page: {{ snapshot.slug }}
- Created by {{ changer.get_full_name() }}
** More info at {{ final_url_name }} ({{ final_url }}) **

View File

@ -1 +1 @@
[{{ object.project.name|safe }}] Created the Wiki Page "{{ object.slug }}"
[{{ project.name|safe }}] Created the Wiki Page "{{ snapshot.slug }}"

View File

@ -4,8 +4,8 @@
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
<tr>
<td>
<h1>{{ object.project.name }}</h1>
<h2>Wiki Page: {{ object.slug }}</h2>
<h1>{{ project.name }}</h1>
<h2>Wiki Page: {{ snapshot.slug }}</h2>
<p>Deleted by <b>{{ changer.get_full_name() }}</b></p>
</td>
</tr>

View File

@ -1,3 +1,3 @@
- Project: {{ object.project.name }}
- Wiki Page: {{ object.slug }}
- Project: {{ project.name }}
- Wiki Page: {{ snapshot.slug }}
- Deleted by {{ changer.get_full_name() }}

View File

@ -1 +1 @@
[{{ object.project.name|safe }}] Deleted the Wiki Page "{{ object.slug }}"
[{{ project.name|safe }}] Deleted the Wiki Page "{{ snapshot.slug }}"

View File

@ -22,7 +22,7 @@ from taiga.base.decorators import list_route
from taiga.base.api import ModelCrudViewSet
from taiga.projects.models import Project
from taiga.projects.notifications import WatchedResourceMixin
from taiga.projects.notifications.mixins import WatchedResourceMixin
from taiga.projects.history.mixins import HistoryResourceMixin
from taiga.projects.occ import OCCResourceMixin

View File

@ -21,7 +21,7 @@ from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from taiga.projects.occ import OCCModelMixin
from taiga.projects.notifications import WatchedModelMixin
from taiga.projects.notifications.mixins import WatchedModelMixin
from taiga.projects.mixins.blocked import BlockedMixin
from taiga.base.tags import TaggedMixin

View File

@ -30,7 +30,7 @@ from taiga.base import exceptions as exc
from taiga.base.decorators import list_route
from taiga.base.api import ModelCrudViewSet
from taiga.projects.notifications import WatchedResourceMixin
from taiga.projects.notifications.mixins import WatchedResourceMixin
from taiga.projects.history.mixins import HistoryResourceMixin
from taiga.projects.occ import OCCResourceMixin
@ -177,4 +177,3 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi
self.send_notifications(self.object.generated_from_issue, history)
return response

View File

@ -21,7 +21,7 @@ from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from taiga.projects.occ import OCCModelMixin
from taiga.projects.notifications import WatchedModelMixin
from taiga.projects.notifications.mixins import WatchedModelMixin
from taiga.projects.mixins.blocked import BlockedMixin
from taiga.base.tags import TaggedMixin

View File

@ -28,7 +28,7 @@ from taiga.base.decorators import list_route
from taiga.projects.models import Project
from taiga.mdrender.service import render as mdrender
from taiga.projects.notifications import WatchedResourceMixin
from taiga.projects.notifications.mixins import WatchedResourceMixin
from taiga.projects.history.mixins import HistoryResourceMixin
from taiga.projects.occ import OCCResourceMixin

View File

@ -19,7 +19,7 @@ from django.contrib.contenttypes import generic
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from taiga.projects.notifications import WatchedModelMixin
from taiga.projects.notifications.mixins import WatchedModelMixin
from taiga.projects.occ import OCCModelMixin

View File

@ -17,20 +17,23 @@
import json
import pytest
import time
from unittest.mock import MagicMock, patch
from django.core.urlresolvers import reverse
from django.apps import apps
from .. import factories as f
from .. utils import set_settings
from taiga.projects.notifications import services
from taiga.projects.notifications import models
from taiga.projects.notifications.choices import NotifyLevel
from taiga.projects.history.choices import HistoryType
from taiga.projects.history.services import take_snapshot
from taiga.projects.issues.serializers import IssueSerializer
from taiga.projects.userstories.serializers import UserStorySerializer
from taiga.projects.tasks.serializers import TaskSerializer
pytestmark = pytest.mark.django_db
@ -118,7 +121,7 @@ def test_users_to_notify():
# Test basic description modifications
issue.description = "test1"
issue.save()
users = services.get_users_to_notify(issue, history=history)
users = services.get_users_to_notify(issue)
assert len(users) == 1
assert tuple(users)[0] == issue.get_owner()
@ -126,13 +129,13 @@ def test_users_to_notify():
policy1.notify_level = NotifyLevel.watch
policy1.save()
users = services.get_users_to_notify(issue, history=history)
users = services.get_users_to_notify(issue)
assert len(users) == 2
assert users == {member1.user, issue.get_owner()}
# Test with watchers
issue.watchers.add(member3.user)
users = services.get_users_to_notify(issue, history=history)
users = services.get_users_to_notify(issue)
assert len(users) == 3
assert users == {member1.user, member3.user, issue.get_owner()}
@ -141,90 +144,87 @@ def test_users_to_notify():
policy3.save()
issue.watchers.add(member3.user)
users = services.get_users_to_notify(issue, history=history)
users = services.get_users_to_notify(issue)
assert len(users) == 2
assert users == {member1.user, issue.get_owner()}
@set_settings(CHANGE_NOTIFICATIONS_MIN_INTERVAL=1)
def test_send_notifications_using_services_method(mail):
project = f.ProjectFactory.create()
member1 = f.MembershipFactory.create(project=project)
member2 = f.MembershipFactory.create(project=project)
history_change = MagicMock()
history_change.owner = member1.user
history_change.user = {"pk": member1.user.pk}
history_change.comment = ""
history_change.type = HistoryType.change
history_create = MagicMock()
history_create.owner = member1.user
history_create.user = {"pk": member1.user.pk}
history_create.comment = ""
history_create.type = HistoryType.create
history_delete = MagicMock()
history_delete.owner = member1.user
history_delete.user = {"pk": member1.user.pk}
history_delete.comment = ""
history_delete.type = HistoryType.delete
# Issues
issue = f.IssueFactory.create(project=project)
take_snapshot(issue)
services.send_notifications(issue,
history=history_create,
users={member1.user, member2.user})
history=history_create)
services.send_notifications(issue,
history=history_change,
users={member1.user, member2.user})
history=history_change)
services.send_notifications(issue,
history=history_delete,
users={member1.user, member2.user})
history=history_delete)
# Userstories
us = f.UserStoryFactory.create()
take_snapshot(us)
services.send_notifications(us,
history=history_create,
users={member1.user, member2.user})
history=history_create)
services.send_notifications(us,
history=history_change,
users={member1.user, member2.user})
history=history_change)
services.send_notifications(us,
history=history_delete,
users={member1.user, member2.user})
history=history_delete)
# Tasks
task = f.TaskFactory.create()
take_snapshot(task)
services.send_notifications(task,
history=history_create,
users={member1.user, member2.user})
history=history_create)
services.send_notifications(task,
history=history_change,
users={member1.user, member2.user})
history=history_change)
services.send_notifications(task,
history=history_delete,
users={member1.user, member2.user})
history=history_delete)
# Wiki pages
wiki = f.WikiPageFactory.create()
take_snapshot(wiki)
services.send_notifications(wiki,
history=history_create,
users={member1.user, member2.user})
history=history_create)
services.send_notifications(wiki,
history=history_change,
users={member1.user, member2.user})
history=history_change)
services.send_notifications(wiki,
history=history_delete,
users={member1.user, member2.user})
assert len(mail.outbox) == 24
history=history_delete)
assert models.HistoryChangeNotification.objects.count() == 12
assert len(mail.outbox) == 0
time.sleep(1)
services.process_sync_notifications()
assert len(mail.outbox) == 12
@set_settings(CHANGE_NOTIFICATIONS_MIN_INTERVAL=1)
def test_resource_notification_test(client, mail):
user1 = f.UserFactory.create()
user2 = f.UserFactory.create()
@ -242,13 +242,23 @@ def test_resource_notification_test(client, mail):
with patch(mock_path) as m:
data = {"subject": "Fooooo", "version": issue.version}
response = client.patch(url, json.dumps(data), content_type="application/json")
assert len(mail.outbox) == 1
assert response.status_code == 200
assert len(mail.outbox) == 0
assert models.HistoryChangeNotification.objects.count() == 1
time.sleep(1)
services.process_sync_notifications()
assert len(mail.outbox) == 1
assert models.HistoryChangeNotification.objects.count() == 0
with patch(mock_path) as m:
response = client.delete(url)
assert response.status_code == 204
assert len(mail.outbox) == 1
assert models.HistoryChangeNotification.objects.count() == 1
time.sleep(1)
services.process_sync_notifications()
assert len(mail.outbox) == 2
assert models.HistoryChangeNotification.objects.count() == 0
def test_watchers_assignation_for_issue(client):