Merge pull request #144 from taigaio/notification-refactor
Notification refactorremotes/origin/enhancement/email-actions
commit
dc90036e55
|
@ -332,6 +332,10 @@ TAGS_PREDEFINED_COLORS = ["#fce94f", "#edd400", "#c4a000", "#8ae234",
|
||||||
FEEDBACK_ENABLED = True
|
FEEDBACK_ENABLED = True
|
||||||
FEEDBACK_EMAIL = "support@taiga.io"
|
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
|
# NOTE: DON'T INSERT MORE SETTINGS AFTER THIS LINE
|
||||||
TEST_RUNNER="django.test.runner.DiscoverRunner"
|
TEST_RUNNER="django.test.runner.DiscoverRunner"
|
||||||
|
|
|
@ -31,7 +31,7 @@ from taiga.base import filters
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
from taiga.users.models import User
|
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 taiga.projects.history.mixins import HistoryResourceMixin
|
||||||
|
|
||||||
from . import permissions
|
from . import permissions
|
||||||
|
|
|
@ -76,6 +76,18 @@ class HistoryEntry(models.Model):
|
||||||
# snapshot. The rest are partial snapshot.
|
# snapshot. The rest are partial snapshot.
|
||||||
is_snapshot = models.BooleanField(default=False)
|
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
|
@cached_property
|
||||||
def owner(self):
|
def owner(self):
|
||||||
pk = self.user["pk"]
|
pk = self.user["pk"]
|
||||||
|
@ -198,4 +210,3 @@ class HistoryEntry(models.Model):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["created_at"]
|
ordering = ["created_at"]
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,14 @@ def make_key_from_model_object(obj:object) -> str:
|
||||||
return "{0}:{1}".format(tn, obj.pk)
|
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):
|
def register_values_implementation(typename:str, fn=None):
|
||||||
"""
|
"""
|
||||||
Register values implementation for specified typename.
|
Register values implementation for specified typename.
|
||||||
|
|
|
@ -99,12 +99,12 @@
|
||||||
{# DESCRIPTIONS #}
|
{# DESCRIPTIONS #}
|
||||||
{% elif field_name in ["description_diff"] %}
|
{% elif field_name in ["description_diff"] %}
|
||||||
<dd style="background: #eee; padding: 5px 15px; color: #444">
|
<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>
|
</dd>
|
||||||
{# CONTENT #}
|
{# CONTENT #}
|
||||||
{% elif field_name in ["content_diff"] %}
|
{% elif field_name in ["content_diff"] %}
|
||||||
<dd style="background: #eee; padding: 5px 15px; color: #444">
|
<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>
|
</dd>
|
||||||
{# ASSIGNED TO #}
|
{# ASSIGNED TO #}
|
||||||
{% elif field_name == "assigned_to" %}
|
{% elif field_name == "assigned_to" %}
|
||||||
|
|
|
@ -30,7 +30,7 @@ from taiga.base import tags
|
||||||
|
|
||||||
from taiga.users.models import User
|
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.occ import OCCResourceMixin
|
||||||
from taiga.projects.history.mixins import HistoryResourceMixin
|
from taiga.projects.history.mixins import HistoryResourceMixin
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ from django.dispatch import receiver
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from taiga.projects.occ import OCCModelMixin
|
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.projects.mixins.blocked import BlockedMixin
|
||||||
from taiga.base.tags import TaggedMixin
|
from taiga.base.tags import TaggedMixin
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ from taiga.base import exceptions as exc
|
||||||
from taiga.base.decorators import detail_route
|
from taiga.base.decorators import detail_route
|
||||||
from taiga.base.api import ModelCrudViewSet
|
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.history.mixins import HistoryResourceMixin
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
from taiga.base.utils.slug import slugify_uniquely
|
from taiga.base.utils.slug import slugify_uniquely
|
||||||
from taiga.base.utils.dicts import dict_sum
|
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
|
from taiga.projects.userstories.models import UserStory
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
|
|
|
@ -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"]
|
|
|
@ -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)
|
|
@ -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,),
|
||||||
|
),
|
||||||
|
]
|
|
@ -62,8 +62,7 @@ class WatchedResourceMixin(object):
|
||||||
|
|
||||||
# Get a complete list of notifiable users for current
|
# Get a complete list of notifiable users for current
|
||||||
# object and send the change notification to them.
|
# object and send the change notification to them.
|
||||||
users = services.get_users_to_notify(obj, history=history)
|
services.send_notifications(obj, history=history)
|
||||||
services.send_notifications(obj, history=history, users=users)
|
|
||||||
|
|
||||||
def post_save(self, obj, created=False):
|
def post_save(self, obj, created=False):
|
||||||
self.send_notifications(obj)
|
self.send_notifications(obj)
|
||||||
|
|
|
@ -19,7 +19,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from .choices import NOTIFY_LEVEL_CHOICES
|
from .choices import NOTIFY_LEVEL_CHOICES
|
||||||
|
from taiga.projects.history.choices import HISTORY_TYPE_CHOICES
|
||||||
|
|
||||||
class NotifyPolicy(models.Model):
|
class NotifyPolicy(models.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -43,3 +43,27 @@ class NotifyPolicy(models.Model):
|
||||||
self.modified_at = timezone.now()
|
self.modified_at = timezone.now()
|
||||||
|
|
||||||
return super().save(*args, **kwargs)
|
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)
|
||||||
|
|
|
@ -19,6 +19,9 @@ from functools import partial
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.db import IntegrityError
|
from django.db import IntegrityError
|
||||||
from django.contrib.contenttypes.models import ContentType
|
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
|
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.base.utils.text import strip_lines
|
||||||
from taiga.projects.notifications.choices import NotifyLevel
|
from taiga.projects.notifications.choices import NotifyLevel
|
||||||
from taiga.projects.history.choices import HistoryType
|
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:
|
def notify_policy_exists(project, user) -> bool:
|
||||||
"""
|
"""
|
||||||
|
@ -113,7 +121,7 @@ def analize_object_for_watchers(obj:object, history:object):
|
||||||
obj.watchers.add(user)
|
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
|
Get filtered set of users to notify for specified
|
||||||
model instance and changer.
|
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()))
|
candidates.update(filter(_can_notify_light, obj.get_participants()))
|
||||||
|
|
||||||
# Remove the changer from candidates
|
# Remove the changer from candidates
|
||||||
candidates.discard(history.owner)
|
if discard_users:
|
||||||
|
candidates = candidates - set(discard_users)
|
||||||
|
|
||||||
return frozenset(candidates)
|
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,
|
Ginven an changed model instance and change type,
|
||||||
return the preformated template name for it.
|
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"
|
# Resolve integer enum value from "change_type"
|
||||||
# parameter to human readable string
|
# parameter to human readable string
|
||||||
if change_type == HistoryType.create:
|
if change_type == HistoryType.create:
|
||||||
|
@ -158,7 +166,6 @@ def _resolve_template_name(obj, *, change_type:int) -> str:
|
||||||
change_type = "change"
|
change_type = "change"
|
||||||
else:
|
else:
|
||||||
change_type = "delete"
|
change_type = "delete"
|
||||||
|
|
||||||
tmpl = "{app_label}/{model}-{change}"
|
tmpl = "{app_label}/{model}-{change}"
|
||||||
return tmpl.format(app_label=ct.app_label,
|
return tmpl.format(app_label=ct.app_label,
|
||||||
model=ct.model,
|
model=ct.model,
|
||||||
|
@ -178,19 +185,63 @@ def _make_template_mail(name:str):
|
||||||
return cls()
|
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
|
a complete list for users to notify, send
|
||||||
email to all users.
|
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)
|
email = _make_template_mail(template_name)
|
||||||
|
|
||||||
for user in users:
|
for user in notification.notify_users.distinct():
|
||||||
email.send(user.email, context)
|
email.send(user.email, context)
|
||||||
|
|
||||||
|
notification.delete()
|
||||||
|
|
||||||
|
|
||||||
|
def process_sync_notifications():
|
||||||
|
for notification in HistoryChangeNotification.objects.all():
|
||||||
|
send_sync_notifications(notification.pk)
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
{% extends "emails/base.jinja" %}
|
{% extends "emails/base.jinja" %}
|
||||||
|
|
||||||
{% set final_url = resolve_front_url("issue", object.project.slug, object.ref) %}
|
{% set final_url = resolve_front_url("issue", project.slug, snapshot.ref) %}
|
||||||
{% set final_url_name = "Taiga - View issue #{0}".format(object.ref) %}
|
{% set final_url_name = "Taiga - View issue #{0}".format(snapshot.ref) %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<h1>Project: {{ object.project.name }}</h1>
|
<h1>Project: {{ project.name }}</h1>
|
||||||
<h2>Issue #{{ object.ref }}: {{ object.subject }}</h2>
|
<h2>Issue #{{ snapshot.ref }}: {{ snapshot.subject }}</h2>
|
||||||
<p>Updated by <b>{{ changer.get_full_name() }}</b>.</p>
|
<p>Updated by <b>{{ changer.get_full_name() }}</b>.</p>
|
||||||
{% if comment %}
|
{% for entry in history_entries%}
|
||||||
<p>Comment <b>{{ mdrender(object.project, comment) }}</b></p>
|
{% if entry.comment %}
|
||||||
|
<p>Comment <b>{{ mdrender(project, entry.comment) }}</b></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% set changed_fields = entry.values_diff %}
|
||||||
{% if changed_fields %}
|
{% if changed_fields %}
|
||||||
<p>Updated fields:</p>
|
|
||||||
{% include "emails/includes/fields_diff-html.jinja" %}
|
{% include "emails/includes/fields_diff-html.jinja" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
{% set final_url = resolve_front_url("issue", object.project.slug, object.ref) %}
|
{% set final_url = resolve_front_url("issue", project.slug, snapshot.ref) %}
|
||||||
{% set final_url_name = "Taiga - View issue #{0}".format(object.ref) %}
|
{% set final_url_name = "Taiga - View issue #{0}".format(snapshot.ref) %}
|
||||||
|
|
||||||
- Project: {{ object.project.name }}
|
- Project: {{ project.name }}
|
||||||
- Issue #{{ object.ref }}: {{ object.subject }}
|
- Issue #{{ snapshot.ref }}: {{ snapshot.subject }}
|
||||||
- Updated by {{ changer.get_full_name() }}
|
- Updated by {{ changer.get_full_name() }}
|
||||||
{% if comment %}
|
{% for entry in history_entries%}
|
||||||
Comment: {{ comment|linebreaksbr }}
|
{% if entry.comment %}
|
||||||
{% endif %}
|
Comment: {{ entry.comment|linebreaksbr }}
|
||||||
{% if changed_fields %}
|
{% endif %}
|
||||||
- Updated fields:
|
{% set changed_fields = entry.values_diff %}
|
||||||
|
{% if changed_fields %}
|
||||||
{% include "emails/includes/fields_diff-text.jinja" %}
|
{% include "emails/includes/fields_diff-text.jinja" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
** More info at {{ final_url_name }} ({{ final_url }}) **
|
** More info at {{ final_url_name }} ({{ final_url }}) **
|
||||||
|
|
|
@ -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 }}"
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
{% extends "emails/base.jinja" %}
|
{% extends "emails/base.jinja" %}
|
||||||
|
|
||||||
{% set final_url = resolve_front_url("issue", object.project.slug, object.ref) %}
|
{% set final_url = resolve_front_url("issue", project.slug, snapshot.ref) %}
|
||||||
{% set final_url_name = "Taiga - View issue #{0}".format(object.ref) %}
|
{% set final_url_name = "Taiga - View issue #{0}".format(snapshot.ref) %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<h1>Project: {{ object.project.name }}</h1>
|
<h1>Project: {{ project.name }}</h1>
|
||||||
<h2>Issue #{{ object.ref }}: {{ object.subject }}</h2>
|
<h2>Issue #{{ snapshot.ref }}: {{ snapshot.subject }}</h2>
|
||||||
<p>Created by <b>{{ changer.get_full_name() }}</b>.</p>
|
<p>Created by <b>{{ changer.get_full_name() }}</b>.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{% set final_url = resolve_front_url("issue", object.project.slug, object.ref) %}
|
{% set final_url = resolve_front_url("issue", project.slug, snapshot.ref) %}
|
||||||
{% set final_url_name = "Taiga - View issue #{0}".format(object.ref) %}
|
{% set final_url_name = "Taiga - View issue #{0}".format(snapshot.ref) %}
|
||||||
|
|
||||||
- Project: {{ object.project.name }}
|
- Project: {{ project.name }}
|
||||||
- US #{{ object.ref }}: {{ object.subject }}
|
- US #{{ snapshot.ref }}: {{ snapshot.subject }}
|
||||||
- Created by {{ changer.get_full_name() }}
|
- Created by {{ changer.get_full_name() }}
|
||||||
|
|
||||||
** More info at {{ final_url_name }} ({{ final_url }}) **
|
** More info at {{ final_url_name }} ({{ final_url }}) **
|
||||||
|
|
|
@ -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 }}"
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<h1>{{ object.project.name }}</h1>
|
<h1>{{ project.name }}</h1>
|
||||||
<h2>Issue #{{ object.ref }}: {{ object.subject }}</h2>
|
<h2>Issue #{{ snapshot.ref }}: {{ snapshot.subject }}</h2>
|
||||||
<p>Deleted by <b>{{ changer.get_full_name() }}</b></p>
|
<p>Deleted by <b>{{ changer.get_full_name() }}</b></p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
- Project: {{ object.project.name }}
|
- Project: {{ project.name }}
|
||||||
- Issue #{{ object.ref }}: {{ object.subject }}
|
- Issue #{{ snapshot.ref }}: {{ snapshot.subject }}
|
||||||
- Deleted by {{ changer.get_full_name() }}
|
- Deleted by {{ changer.get_full_name() }}
|
||||||
|
|
|
@ -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 }}"
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
{% extends "emails/base.jinja" %}
|
{% extends "emails/base.jinja" %}
|
||||||
|
|
||||||
{% set final_url = resolve_front_url("taskboard", object.project.slug, object.slug) %}
|
{% set final_url = resolve_front_url("taskboard", project.slug, snapshot.slug) %}
|
||||||
{% set final_url_name = "Taiga - View milestone #{0}".format(object.slug) %}
|
{% set final_url_name = "Taiga - View milestone #{0}".format(snapshot.slug) %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<h1>Project: {{ object.project.name }}</h1>
|
<h1>Project: {{ project.name }}</h1>
|
||||||
<h2>Milestone #{{ object.slug }}: {{ object.name }}</h2>
|
<h2>Milestone #{{ snapshot.slug }}: {{ snapshot.name }}</h2>
|
||||||
<p>Updated by <b>{{ changer.get_full_name() }}</b>.</p>
|
<p>Updated by <b>{{ changer.get_full_name() }}</b>.</p>
|
||||||
{% if comment %}
|
{% for entry in history_entries%}
|
||||||
<p>Comment <b>{{ comment|linebreaksbr }}</b></p>
|
{% if entry.comment %}
|
||||||
|
<p>Comment <b>{{ entry.comment|linebreaksbr }}</b></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% set changed_fields = entry.values_diff %}
|
||||||
{% if changed_fields %}
|
{% if changed_fields %}
|
||||||
<p>Updated fields:</p>
|
|
||||||
{% include "emails/includes/fields_diff-html.jinja" %}
|
{% include "emails/includes/fields_diff-html.jinja" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
{% set final_url = resolve_front_url("taskboard", object.project.slug, object.slug) %}
|
{% set final_url = resolve_front_url("taskboard", project.slug, snapshot.slug) %}
|
||||||
{% set final_url_name = "Taiga - View milestone #{0}".format(object.slug) %}
|
{% set final_url_name = "Taiga - View milestone #{0}".format(snapshot.slug) %}
|
||||||
|
|
||||||
- Project: {{ object.project.name }}
|
- Project: {{ project.name }}
|
||||||
- Milestone #{{ object.slug }}: {{ object.name }}
|
- Milestone #{{ snapshot.slug }}: {{ snapshot.name }}
|
||||||
- Updated by {{ changer.get_full_name() }}
|
- Updated by {{ changer.get_full_name() }}
|
||||||
{% if comment %}
|
{% for entry in history_entries%}
|
||||||
Comment: {{ comment|linebreaksbr }}
|
{% if entry.comment %}
|
||||||
{% endif %}
|
Comment: {{ entry.comment|linebreaksbr }}
|
||||||
{% if changed_fields %}
|
{% endif %}
|
||||||
- Updated fields:
|
{% set changed_fields = entry.values_diff %}
|
||||||
|
{% if changed_fields %}
|
||||||
{% include "emails/includes/fields_diff-text.jinja" %}
|
{% include "emails/includes/fields_diff-text.jinja" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
** More info at {{ final_url_name }} ({{ final_url }}) **
|
** More info at {{ final_url_name }} ({{ final_url }}) **
|
||||||
|
|
|
@ -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 }}"
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
{% extends "emails/base.jinja" %}
|
{% extends "emails/base.jinja" %}
|
||||||
|
|
||||||
{% set final_url = resolve_front_url("taskboard", object.project.slug, object.slug) %}
|
{% set final_url = resolve_front_url("taskboard", project.slug, snapshot.slug) %}
|
||||||
{% set final_url_name = "Taiga - View milestone #{0}".format(object.slug) %}
|
{% set final_url_name = "Taiga - View milestone #{0}".format(snapshot.slug) %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<h1>Project: {{ object.project.name }}</h1>
|
<h1>Project: {{ project.name }}</h1>
|
||||||
<h2>Milestone #{{ object.slug }}: {{ object.name }}</h2>
|
<h2>Milestone #{{ snapshot.slug }}: {{ snapshot.name }}</h2>
|
||||||
<p>Created by <b>{{ changer.get_full_name() }}</b>.</p>
|
<p>Created by <b>{{ changer.get_full_name() }}</b>.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{% set final_url = resolve_front_url("taskboard", object.project.slug, object.slug) %}
|
{% set final_url = resolve_front_url("taskboard", project.slug, snapshot.slug) %}
|
||||||
{% set final_url_name = "Taiga - View milestone #{0}".format(object.slug) %}
|
{% set final_url_name = "Taiga - View milestone #{0}".format(snapshot.slug) %}
|
||||||
|
|
||||||
- Project: {{ object.project.name }}
|
- Project: {{ project.name }}
|
||||||
- Milestone #{{ object.slug }}: {{ object.name }}
|
- Milestone #{{ snapshot.slug }}: {{ snapshot.name }}
|
||||||
- Created by {{ changer.get_full_name() }}
|
- Created by {{ changer.get_full_name() }}
|
||||||
|
|
||||||
** More info at {{ final_url_name }} ({{ final_url }}) **
|
** More info at {{ final_url_name }} ({{ final_url }}) **
|
||||||
|
|
|
@ -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 }}"
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<h1>{{ object.project.name }}</h1>
|
<h1>{{ project.name }}</h1>
|
||||||
<h2>Milestone #{{ object.slug }}: {{ object.name }}</h2>
|
<h2>Milestone #{{ snapshot.slug }}: {{ snapshot.name }}</h2>
|
||||||
<p>Deleted by <b>{{ changer.get_full_name() }}</b></p>
|
<p>Deleted by <b>{{ changer.get_full_name() }}</b></p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
- Project: {{ object.project.name }}
|
- Project: {{ project.name }}
|
||||||
- Milestone #{{ object.slug }}: {{ object.name }}
|
- Milestone #{{ snapshot.slug }}: {{ snapshot.name }}
|
||||||
- Deleted by {{ changer.get_full_name() }}
|
- Deleted by {{ changer.get_full_name() }}
|
||||||
|
|
|
@ -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 }}"
|
||||||
|
|
|
@ -1,21 +1,23 @@
|
||||||
{% extends "emails/base.jinja" %}
|
{% extends "emails/base.jinja" %}
|
||||||
|
|
||||||
{% set final_url = resolve_front_url("project-admin", object.slug) %}
|
{% set final_url = resolve_front_url("project-admin", snapshot.slug) %}
|
||||||
{% set final_url_name = "Taiga - View Project #{0}".format(object.slug) %}
|
{% set final_url_name = "Taiga - View Project #{0}".format(snapshot.slug) %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<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>
|
<p>Updated by <b>{{ changer.get_full_name() }}</b>.</p>
|
||||||
{% if comment %}
|
{% for entry in history_entries%}
|
||||||
<p>Comment <b>{{ comment|linebreaksbr }}</b></p>
|
{% if entry.comment %}
|
||||||
|
<p>Comment <b>{{ entry.comment|linebreaksbr }}</b></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% set changed_fields = entry.values_diff %}
|
||||||
{% if changed_fields %}
|
{% if changed_fields %}
|
||||||
<p>Updated fields:</p>
|
|
||||||
{% include "emails/includes/fields_diff-html.jinja" %}
|
{% include "emails/includes/fields_diff-html.jinja" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
{% set final_url = resolve_front_url("project-admin", object.slug) %}
|
{% set final_url = resolve_front_url("project-admin", snapshot.slug) %}
|
||||||
{% set final_url_name = "Taiga - View Project #{0}".format(object.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() }}
|
- Updated by {{ changer.get_full_name() }}
|
||||||
{% if comment %}
|
{% for entry in history_entries%}
|
||||||
Comment: {{ comment|linebreaksbr }}
|
{% if entry.comment %}
|
||||||
{% endif %}
|
Comment: {{ entry.comment|linebreaksbr }}
|
||||||
{% if changed_fields %}
|
{% endif %}
|
||||||
- Updated fields:
|
{% set changed_fields = entry.values_diff %}
|
||||||
{% for field_name, values in changed_fields.items() %}
|
{% for field_name, values in changed_fields.items() %}
|
||||||
* {{ verbose_name(object, field_name) }}</b>: from '{{ values.0 }}' to '{{ values.1 }}'.
|
* {{ verbose_name(object, field_name) }}</b>: from '{{ values.0 }}' to '{{ values.1 }}'.
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
** More info at {{ final_url_name }} ({{ final_url }}) **
|
** More info at {{ final_url_name }} ({{ final_url }}) **
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
[{{ object.name|safe }}] Updated the project #{{ object.slug|safe }}
|
[{{ snapshot.name|safe }}] Updated the project #{{ snapshot.slug|safe }}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{% set final_url = resolve_front_url("project-admin", object.slug) %}
|
{% set final_url = resolve_front_url("project-admin", snapshot.slug) %}
|
||||||
{% set final_url_name = "Taiga - View Project #{0}".format(object.slug) %}
|
{% set final_url_name = "Taiga - View Project #{0}".format(snapshot.slug) %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<h1>Project: {{ object.project.name }}</h1>
|
<h1>Project: {{ project.name }}</h1>
|
||||||
<h2>Project #{{ object.slug }}: {{ object.name }}</h2>
|
<h2>Project #{{ snapshot.slug }}: {{ snapshot.name }}</h2>
|
||||||
<p>Created by <b>{{ changer.get_full_name() }}</b>.</p>
|
<p>Created by <b>{{ changer.get_full_name() }}</b>.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{% set final_url = resolve_front_url("project-admin", object.slug) %}
|
{% set final_url = resolve_front_url("project-admin", snapshot.slug) %}
|
||||||
{% set final_url_name = "Taiga - View Project #{0}".format(object.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() }}
|
- Created by {{ changer.get_full_name() }}
|
||||||
|
|
||||||
** More info at {{ final_url_name }} ({{ final_url }}) **
|
** More info at {{ final_url_name }} ({{ final_url }}) **
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
[{{ object.name|safe }}] Created the project #{{ object.slug|safe }}
|
[{{ snapshot.name|safe }}] Created the project #{{ snapshot.slug|safe }}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<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>
|
<p>Deleted by <b>{{ changer.get_full_name() }}</b></p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
- Project #{{ object.slug }}: {{ object.name }}
|
- Project #{{ snapshot.slug }}: {{ snapshot.name }}
|
||||||
- Deleted by {{ changer.get_full_name() }}
|
- Deleted by {{ changer.get_full_name() }}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
[{{ object.name|safe }}] Deleted the project #{{ object.slug|safe }}
|
[{{ snapshot.name|safe }}] Deleted the project #{{ snapshot.slug|safe }}
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
{% extends "emails/base.jinja" %}
|
{% extends "emails/base.jinja" %}
|
||||||
|
|
||||||
{% set final_url = resolve_front_url("task", object.project.slug, object.ref) %}
|
{% set final_url = resolve_front_url("task", project.slug, snapshot.ref) %}
|
||||||
{% set final_url_name = "Taiga - View task #{0}".format(object.ref) %}
|
{% set final_url_name = "Taiga - View task #{0}".format(snapshot.ref) %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<h1>Project: {{ object.project.name }}</h1>
|
<h1>Project: {{ project.name }}</h1>
|
||||||
<h2>Task #{{ object.ref }}: {{ object.subject }}</h2>
|
<h2>Task #{{ snapshot.ref }}: {{ snapshot.subject }}</h2>
|
||||||
<p>Updated by <b>{{ changer.get_full_name() }}</b>.</p>
|
<p>Updated by <b>{{ changer.get_full_name() }}</b>.</p>
|
||||||
{% if comment %}
|
{% for entry in history_entries%}
|
||||||
<p>Comment <b>{{ mdrender(object.project, comment) }}</b></p>
|
{% if entry.comment %}
|
||||||
|
<p>Comment <b>{{ mdrender(project, entry.comment) }}</b></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% set changed_fields = entry.values_diff %}
|
||||||
{% if changed_fields %}
|
{% if changed_fields %}
|
||||||
<p>Updated fields:</p>
|
|
||||||
{% include "emails/includes/fields_diff-html.jinja" %}
|
{% include "emails/includes/fields_diff-html.jinja" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
{% set final_url = resolve_front_url("task", object.project.slug, object.ref) %}
|
{% set final_url = resolve_front_url("task", project.slug, snapshot.ref) %}
|
||||||
{% set final_url_name = "Taiga - View task #{0}".format(object.ref) %}
|
{% set final_url_name = "Taiga - View task #{0}".format(snapshot.ref) %}
|
||||||
|
|
||||||
- Project: {{ object.project.name }}
|
- Project: {{ project.name }}
|
||||||
- Task #{{ object.ref }}: {{ object.subject }}
|
- Task #{{ snapshot.ref }}: {{ snapshot.subject }}
|
||||||
- Updated by {{ changer.get_full_name() }}
|
- Updated by {{ changer.get_full_name() }}
|
||||||
{% if comment %}
|
{% for entry in history_entries%}
|
||||||
Comment: {{ comment|linebreaksbr }}
|
{% if entry.comment %}
|
||||||
{% endif %}
|
Comment: {{ entry.comment|linebreaksbr }}
|
||||||
{% if changed_fields %}
|
{% endif %}
|
||||||
- Updated fields:
|
{% set changed_fields = entry.values_diff %}
|
||||||
|
{% if changed_fields %}
|
||||||
{% include "emails/includes/fields_diff-text.jinja" %}
|
{% include "emails/includes/fields_diff-text.jinja" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
** More info at {{ final_url_name }} ({{ final_url }}) **
|
** More info at {{ final_url_name }} ({{ final_url }}) **
|
||||||
|
|
|
@ -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 }}"
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
{% extends "emails/base.jinja" %}
|
{% extends "emails/base.jinja" %}
|
||||||
|
|
||||||
{% set final_url = resolve_front_url("task", object.project.slug, object.ref) %}
|
{% set final_url = resolve_front_url("task", project.slug, snapshot.ref) %}
|
||||||
{% set final_url_name = "Taiga - View task #{0}".format(object.ref) %}
|
{% set final_url_name = "Taiga - View task #{0}".format(snapshot.ref) %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<h1>Project: {{ object.project.name }}</h1>
|
<h1>Project: {{ project.name }}</h1>
|
||||||
<h2>Task #{{ object.ref }}: {{ object.subject }}</h2>
|
<h2>Task #{{ snapshot.ref }}: {{ snapshot.subject }}</h2>
|
||||||
<p>Created by <b>{{ changer.get_full_name() }}</b>.</p>
|
<p>Created by <b>{{ changer.get_full_name() }}</b>.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{% set final_url = resolve_front_url("task", object.project.slug, object.ref) %}
|
{% set final_url = resolve_front_url("task", project.slug, snapshot.ref) %}
|
||||||
{% set final_url_name = "Taiga - View task #{0}".format(object.ref) %}
|
{% set final_url_name = "Taiga - View task #{0}".format(snapshot.ref) %}
|
||||||
|
|
||||||
- Project: {{ object.project.name }}
|
- Project: {{ project.name }}
|
||||||
- Task #{{ object.ref }}: {{ object.subject }}
|
- Task #{{ snapshot.ref }}: {{ snapshot.subject }}
|
||||||
- Created by {{ changer.get_full_name() }}
|
- Created by {{ changer.get_full_name() }}
|
||||||
|
|
||||||
** More info at {{ final_url_name }} ({{ final_url }}) **
|
** More info at {{ final_url_name }} ({{ final_url }}) **
|
||||||
|
|
|
@ -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 }}"
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<h1>{{ object.project.name }}</h1>
|
<h1>{{ project.name }}</h1>
|
||||||
<h2>Task #{{ object.ref }}: {{ object.subject }}</h2>
|
<h2>Task #{{ snapshot.ref }}: {{ snapshot.subject }}</h2>
|
||||||
<p>Deleted by <b>{{ changer.get_full_name() }}</b></p>
|
<p>Deleted by <b>{{ changer.get_full_name() }}</b></p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
- Project: {{ object.project.name }}
|
- Project: {{ project.name }}
|
||||||
- Task #{{ object.ref }}: {{ object.subject }}
|
- Task #{{ snapshot.ref }}: {{ snapshot.subject }}
|
||||||
- Deleted by {{ changer.get_full_name() }}
|
- Deleted by {{ changer.get_full_name() }}
|
||||||
|
|
||||||
|
|
|
@ -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 }}"
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
{% extends "emails/base.jinja" %}
|
{% extends "emails/base.jinja" %}
|
||||||
|
|
||||||
{% set final_url = resolve_front_url("userstory", object.project.slug, object.ref) %}
|
{% set final_url = resolve_front_url("userstory", project.slug, snapshot.ref) %}
|
||||||
{% set final_url_name = "Taiga - View US #{0}".format(object.ref) %}
|
{% set final_url_name = "Taiga - View US #{0}".format(snapshot.ref) %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<h1>Project: {{ object.project.name }}</h1>
|
<h1>Project: {{ project.name }}</h1>
|
||||||
<h2>US #{{ object.ref }}: {{ object.subject }}</h2>
|
<h2>US #{{ snapshot.ref }}: {{ snapshot.subject }}</h2>
|
||||||
<p>Updated by <b>{{ changer.get_full_name() }}</b>.</p>
|
<p>Updated by <b>{{ changer.get_full_name() }}</b>.</p>
|
||||||
{% if comment %}
|
{% for entry in history_entries%}
|
||||||
<p>Comment <b>{{ mdrender(object.project, comment)}}</b></p>
|
{% if entry.comment %}
|
||||||
|
<p>Comment <b>{{ mdrender(project, entry.comment) }}</b></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% set changed_fields = entry.values_diff %}
|
||||||
{% if changed_fields %}
|
{% if changed_fields %}
|
||||||
<p>Updated fields:</p>
|
|
||||||
{% include "emails/includes/fields_diff-html.jinja" %}
|
{% include "emails/includes/fields_diff-html.jinja" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
{% set final_url = resolve_front_url("userstory", object.project.slug, object.ref) %}
|
{% set final_url = resolve_front_url("userstory", project.slug, snapshot.ref) %}
|
||||||
{% set final_url_name = "Taiga - View US #{0}".format(object.ref) %}
|
{% set final_url_name = "Taiga - View US #{0}".format(snapshot.ref) %}
|
||||||
|
|
||||||
- Project: {{ object.project.name }}
|
- Project: {{ project.name }}
|
||||||
- US #{{ object.ref }}: {{ object.subject }}
|
- US #{{ snapshot.ref }}: {{ snapshot.subject }}
|
||||||
- Updated by {{ changer.get_full_name() }}
|
- Updated by {{ changer.get_full_name() }}
|
||||||
{% if comment %}
|
{% for entry in history_entries%}
|
||||||
Comment: {{ comment|linebreaksbr }}
|
{% if entry.comment %}
|
||||||
{% endif %}
|
Comment: {{ entry.comment|linebreaksbr }}
|
||||||
{% if changed_fields %}
|
{% endif %}
|
||||||
- Updated fields:
|
{% set changed_fields = entry.values_diff %}
|
||||||
|
{% if changed_fields %}
|
||||||
{% include "emails/includes/fields_diff-text.jinja" %}
|
{% include "emails/includes/fields_diff-text.jinja" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
** More info at {{ final_url_name }} ({{ final_url }}) **
|
** More info at {{ final_url_name }} ({{ final_url }}) **
|
||||||
|
|
|
@ -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 }}"
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
{% extends "emails/base.jinja" %}
|
{% extends "emails/base.jinja" %}
|
||||||
|
|
||||||
{% set final_url = resolve_front_url("userstory", object.project.slug, object.ref) %}
|
{% set final_url = resolve_front_url("userstory", project.slug, snapshot.ref) %}
|
||||||
{% set final_url_name = "Taiga - View US #{0}".format(object.ref) %}
|
{% set final_url_name = "Taiga - View US #{0}".format(snapshot.ref) %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<h1>Project: {{ object.project.name }}</h1>
|
<h1>Project: {{ project.name }}</h1>
|
||||||
<h2>US #{{ object.ref }}: {{ object.subject }}</h2>
|
<h2>US #{{ snapshot.ref }}: {{ snapshot.subject }}</h2>
|
||||||
<p>Created by <b>{{ changer.get_full_name() }}</b>.</p>
|
<p>Created by <b>{{ changer.get_full_name() }}</b>.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{% set final_url = resolve_front_url("userstory", object.project.slug, object.ref) %}
|
{% set final_url = resolve_front_url("userstory", project.slug, snapshot.ref) %}
|
||||||
{% set final_url_name = "Taiga - View US #{0}".format(object.ref) %}
|
{% set final_url_name = "Taiga - View US #{0}".format(snapshot.ref) %}
|
||||||
|
|
||||||
- Project: {{ object.project.name }}
|
- Project: {{ project.name }}
|
||||||
- US #{{ object.ref }}: {{ object.subject }}
|
- US #{{ snapshot.ref }}: {{ snapshot.subject }}
|
||||||
- Created by {{ changer.get_full_name() }}
|
- Created by {{ changer.get_full_name() }}
|
||||||
|
|
||||||
** More info at {{ final_url_name }} ({{ final_url }}) **
|
** More info at {{ final_url_name }} ({{ final_url }}) **
|
||||||
|
|
|
@ -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 }}"
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<h1>{{ object.project.name }}</h1>
|
<h1>{{ project.name }}</h1>
|
||||||
<h2>US #{{ object.ref }}: {{ object.subject }}</h2>
|
<h2>US #{{ snapshot.ref }}: {{ snapshot.subject }}</h2>
|
||||||
<p>Deleted by <b>{{ changer.get_full_name() }}</b></p>
|
<p>Deleted by <b>{{ changer.get_full_name() }}</b></p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
- Project: {{ object.project.name }}
|
- Project: {{ project.name }}
|
||||||
- US #{{ object.ref }}: {{ object.subject }}
|
- US #{{ snapshot.ref }}: {{ snapshot.subject }}
|
||||||
- Deleted by {{ changer.get_full_name() }}
|
- Deleted by {{ changer.get_full_name() }}
|
||||||
|
|
|
@ -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 }}"
|
||||||
|
|
|
@ -1,22 +1,24 @@
|
||||||
{% extends "emails/base.jinja" %}
|
{% extends "emails/base.jinja" %}
|
||||||
|
|
||||||
{% set final_url = resolve_front_url("wiki", object.project.slug, object.slug) %}
|
{% set final_url = resolve_front_url("wiki", project.slug, snapshot.slug) %}
|
||||||
{% set final_url_name = "Taiga - View Wiki Page '{0}'".format(object.slug) %}
|
{% set final_url_name = "Taiga - View Wiki Page '{0}'".format(snapshot.slug) %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<h1>Project: {{ object.project.name }}</h1>
|
<h1>Project: {{ project.name }}</h1>
|
||||||
<h2>Wiki Page: {{ object.slug }}</h2>
|
<h2>Wiki Page: {{ snapshot.slug }}</h2>
|
||||||
<p>Updated by <b>{{ changer.get_full_name() }}</b>.</p>
|
<p>Updated by <b>{{ changer.get_full_name() }}</b>.</p>
|
||||||
{% if comment %}
|
{% for entry in history_entries%}
|
||||||
<p>Comment <b>{{ mdrender(object.project, comment) }}</b></p>
|
{% if entry.comment %}
|
||||||
|
<p>Comment <b>{{ mdrender(project, entry.comment) }}</b></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% set changed_fields = entry.values_diff %}
|
||||||
{% if changed_fields %}
|
{% if changed_fields %}
|
||||||
<p>Updated fields:</p>
|
|
||||||
{% include "emails/includes/fields_diff-html.jinja" %}
|
{% include "emails/includes/fields_diff-html.jinja" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
{% set final_url = resolve_front_url("wiki", object.project.slug, object.slug) %}
|
{% set final_url = resolve_front_url("wiki", project.slug, snapshot.slug) %}
|
||||||
{% set final_url_name = "Taiga - View Wiki Page '{0}'".format(object.slug) %}
|
{% set final_url_name = "Taiga - View Wiki Page '{0}'".format(snapshot.slug) %}
|
||||||
|
|
||||||
- Project: {{ object.project.name }}
|
- Project: {{ project.name }}
|
||||||
- Wiki Page: {{ object.slug }}
|
- Wiki Page: {{ snapshot.slug }}
|
||||||
- Updated by {{ changer.get_full_name() }}
|
- Updated by {{ changer.get_full_name() }}
|
||||||
{% if comment %}
|
{% for entry in history_entries%}
|
||||||
Comment: {{ comment|linebreaksbr }}
|
{% if entry.comment %}
|
||||||
{% endif %}
|
Comment: {{ entry.comment|linebreaksbr }}
|
||||||
{% if changed_fields %}
|
{% endif %}
|
||||||
- Updated fields:
|
{% set changed_fields = entry.values_diff %}
|
||||||
|
{% if changed_fields %}
|
||||||
{% include "emails/includes/fields_diff-text.jinja" %}
|
{% include "emails/includes/fields_diff-text.jinja" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
** More info at {{ final_url_name }} ({{ final_url }}) **
|
** More info at {{ final_url_name }} ({{ final_url }}) **
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
[{{ object.project.name|safe }}] Updated the Wiki Page "{{ object.slug }}"
|
[{{ project.name|safe }}] Updated the Wiki Page "{{ snapshot.slug }}"
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
{% extends "emails/base.jinja" %}
|
{% extends "emails/base.jinja" %}
|
||||||
|
|
||||||
{% set final_url = resolve_front_url("wiki", object.project.slug, object.slug) %}
|
{% set final_url = resolve_front_url("wiki", project.slug, snapshot.slug) %}
|
||||||
{% set final_url_name = "Taiga - View Wiki Page '{0}'".format(object.slug) %}
|
{% set final_url_name = "Taiga - View Wiki Page '{0}'".format(snapshot.slug) %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<h1>Project: {{ object.project.name }}</h1>
|
<h1>Project: {{ project.name }}</h1>
|
||||||
<h2>Wiki Page: {{ object.slug }}</h2>
|
<h2>Wiki Page: {{ snapshot.slug }}</h2>
|
||||||
<p>Created by <b>{{ changer.get_full_name() }}</b>.</p>
|
<p>Created by <b>{{ changer.get_full_name() }}</b>.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{% set final_url = resolve_front_url("wiki", object.project.slug, object.slug) %}
|
{% set final_url = resolve_front_url("wiki", project.slug, snapshot.slug) %}
|
||||||
{% set final_url_name = "Taiga - View Wiki Page '{0}'".format(object.slug) %}
|
{% set final_url_name = "Taiga - View Wiki Page '{0}'".format(snapshot.slug) %}
|
||||||
|
|
||||||
- Project: {{ object.project.name }}
|
- Project: {{ project.name }}
|
||||||
- Wiki Page: {{ object.slug }}
|
- Wiki Page: {{ snapshot.slug }}
|
||||||
- Created by {{ changer.get_full_name() }}
|
- Created by {{ changer.get_full_name() }}
|
||||||
|
|
||||||
** More info at {{ final_url_name }} ({{ final_url }}) **
|
** More info at {{ final_url_name }} ({{ final_url }}) **
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
[{{ object.project.name|safe }}] Created the Wiki Page "{{ object.slug }}"
|
[{{ project.name|safe }}] Created the Wiki Page "{{ snapshot.slug }}"
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
<table border="0" width="100%" cellpadding="0" cellspacing="0" class="table-body">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<h1>{{ object.project.name }}</h1>
|
<h1>{{ project.name }}</h1>
|
||||||
<h2>Wiki Page: {{ object.slug }}</h2>
|
<h2>Wiki Page: {{ snapshot.slug }}</h2>
|
||||||
<p>Deleted by <b>{{ changer.get_full_name() }}</b></p>
|
<p>Deleted by <b>{{ changer.get_full_name() }}</b></p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
- Project: {{ object.project.name }}
|
- Project: {{ project.name }}
|
||||||
- Wiki Page: {{ object.slug }}
|
- Wiki Page: {{ snapshot.slug }}
|
||||||
- Deleted by {{ changer.get_full_name() }}
|
- Deleted by {{ changer.get_full_name() }}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
[{{ object.project.name|safe }}] Deleted the Wiki Page "{{ object.slug }}"
|
[{{ project.name|safe }}] Deleted the Wiki Page "{{ snapshot.slug }}"
|
||||||
|
|
|
@ -22,7 +22,7 @@ from taiga.base.decorators import list_route
|
||||||
from taiga.base.api import ModelCrudViewSet
|
from taiga.base.api import ModelCrudViewSet
|
||||||
from taiga.projects.models import Project
|
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.history.mixins import HistoryResourceMixin
|
||||||
from taiga.projects.occ import OCCResourceMixin
|
from taiga.projects.occ import OCCResourceMixin
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ from django.utils import timezone
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from taiga.projects.occ import OCCModelMixin
|
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.projects.mixins.blocked import BlockedMixin
|
||||||
from taiga.base.tags import TaggedMixin
|
from taiga.base.tags import TaggedMixin
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ from taiga.base import exceptions as exc
|
||||||
from taiga.base.decorators import list_route
|
from taiga.base.decorators import list_route
|
||||||
from taiga.base.api import ModelCrudViewSet
|
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.history.mixins import HistoryResourceMixin
|
||||||
from taiga.projects.occ import OCCResourceMixin
|
from taiga.projects.occ import OCCResourceMixin
|
||||||
|
|
||||||
|
@ -177,4 +177,3 @@ class UserStoryViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMi
|
||||||
self.send_notifications(self.object.generated_from_issue, history)
|
self.send_notifications(self.object.generated_from_issue, history)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from taiga.projects.occ import OCCModelMixin
|
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.projects.mixins.blocked import BlockedMixin
|
||||||
from taiga.base.tags import TaggedMixin
|
from taiga.base.tags import TaggedMixin
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ from taiga.base.decorators import list_route
|
||||||
from taiga.projects.models import Project
|
from taiga.projects.models import Project
|
||||||
from taiga.mdrender.service import render as mdrender
|
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.history.mixins import HistoryResourceMixin
|
||||||
from taiga.projects.occ import OCCResourceMixin
|
from taiga.projects.occ import OCCResourceMixin
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ from django.contrib.contenttypes import generic
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from taiga.projects.notifications import WatchedModelMixin
|
from taiga.projects.notifications.mixins import WatchedModelMixin
|
||||||
from taiga.projects.occ import OCCModelMixin
|
from taiga.projects.occ import OCCModelMixin
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,20 +17,23 @@
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import pytest
|
import pytest
|
||||||
|
import time
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from .. import factories as f
|
from .. import factories as f
|
||||||
|
from .. utils import set_settings
|
||||||
|
|
||||||
from taiga.projects.notifications import services
|
from taiga.projects.notifications import services
|
||||||
|
from taiga.projects.notifications import models
|
||||||
from taiga.projects.notifications.choices import NotifyLevel
|
from taiga.projects.notifications.choices import NotifyLevel
|
||||||
from taiga.projects.history.choices import HistoryType
|
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.issues.serializers import IssueSerializer
|
||||||
from taiga.projects.userstories.serializers import UserStorySerializer
|
from taiga.projects.userstories.serializers import UserStorySerializer
|
||||||
from taiga.projects.tasks.serializers import TaskSerializer
|
from taiga.projects.tasks.serializers import TaskSerializer
|
||||||
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
@ -118,7 +121,7 @@ def test_users_to_notify():
|
||||||
# Test basic description modifications
|
# Test basic description modifications
|
||||||
issue.description = "test1"
|
issue.description = "test1"
|
||||||
issue.save()
|
issue.save()
|
||||||
users = services.get_users_to_notify(issue, history=history)
|
users = services.get_users_to_notify(issue)
|
||||||
assert len(users) == 1
|
assert len(users) == 1
|
||||||
assert tuple(users)[0] == issue.get_owner()
|
assert tuple(users)[0] == issue.get_owner()
|
||||||
|
|
||||||
|
@ -126,13 +129,13 @@ def test_users_to_notify():
|
||||||
policy1.notify_level = NotifyLevel.watch
|
policy1.notify_level = NotifyLevel.watch
|
||||||
policy1.save()
|
policy1.save()
|
||||||
|
|
||||||
users = services.get_users_to_notify(issue, history=history)
|
users = services.get_users_to_notify(issue)
|
||||||
assert len(users) == 2
|
assert len(users) == 2
|
||||||
assert users == {member1.user, issue.get_owner()}
|
assert users == {member1.user, issue.get_owner()}
|
||||||
|
|
||||||
# Test with watchers
|
# Test with watchers
|
||||||
issue.watchers.add(member3.user)
|
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 len(users) == 3
|
||||||
assert users == {member1.user, member3.user, issue.get_owner()}
|
assert users == {member1.user, member3.user, issue.get_owner()}
|
||||||
|
|
||||||
|
@ -141,90 +144,87 @@ def test_users_to_notify():
|
||||||
policy3.save()
|
policy3.save()
|
||||||
|
|
||||||
issue.watchers.add(member3.user)
|
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 len(users) == 2
|
||||||
assert users == {member1.user, issue.get_owner()}
|
assert users == {member1.user, issue.get_owner()}
|
||||||
|
|
||||||
|
@set_settings(CHANGE_NOTIFICATIONS_MIN_INTERVAL=1)
|
||||||
def test_send_notifications_using_services_method(mail):
|
def test_send_notifications_using_services_method(mail):
|
||||||
project = f.ProjectFactory.create()
|
project = f.ProjectFactory.create()
|
||||||
member1 = f.MembershipFactory.create(project=project)
|
member1 = f.MembershipFactory.create(project=project)
|
||||||
member2 = f.MembershipFactory.create(project=project)
|
member2 = f.MembershipFactory.create(project=project)
|
||||||
|
|
||||||
history_change = MagicMock()
|
history_change = MagicMock()
|
||||||
history_change.owner = member1.user
|
history_change.user = {"pk": member1.user.pk}
|
||||||
history_change.comment = ""
|
history_change.comment = ""
|
||||||
history_change.type = HistoryType.change
|
history_change.type = HistoryType.change
|
||||||
|
|
||||||
history_create = MagicMock()
|
history_create = MagicMock()
|
||||||
history_create.owner = member1.user
|
history_create.user = {"pk": member1.user.pk}
|
||||||
history_create.comment = ""
|
history_create.comment = ""
|
||||||
history_create.type = HistoryType.create
|
history_create.type = HistoryType.create
|
||||||
|
|
||||||
history_delete = MagicMock()
|
history_delete = MagicMock()
|
||||||
history_delete.owner = member1.user
|
history_delete.user = {"pk": member1.user.pk}
|
||||||
history_delete.comment = ""
|
history_delete.comment = ""
|
||||||
history_delete.type = HistoryType.delete
|
history_delete.type = HistoryType.delete
|
||||||
|
|
||||||
# Issues
|
# Issues
|
||||||
issue = f.IssueFactory.create(project=project)
|
issue = f.IssueFactory.create(project=project)
|
||||||
|
take_snapshot(issue)
|
||||||
services.send_notifications(issue,
|
services.send_notifications(issue,
|
||||||
history=history_create,
|
history=history_create)
|
||||||
users={member1.user, member2.user})
|
|
||||||
|
|
||||||
services.send_notifications(issue,
|
services.send_notifications(issue,
|
||||||
history=history_change,
|
history=history_change)
|
||||||
users={member1.user, member2.user})
|
|
||||||
|
|
||||||
services.send_notifications(issue,
|
services.send_notifications(issue,
|
||||||
history=history_delete,
|
history=history_delete)
|
||||||
users={member1.user, member2.user})
|
|
||||||
|
|
||||||
# Userstories
|
# Userstories
|
||||||
us = f.UserStoryFactory.create()
|
us = f.UserStoryFactory.create()
|
||||||
|
take_snapshot(us)
|
||||||
services.send_notifications(us,
|
services.send_notifications(us,
|
||||||
history=history_create,
|
history=history_create)
|
||||||
users={member1.user, member2.user})
|
|
||||||
|
|
||||||
services.send_notifications(us,
|
services.send_notifications(us,
|
||||||
history=history_change,
|
history=history_change)
|
||||||
users={member1.user, member2.user})
|
|
||||||
|
|
||||||
services.send_notifications(us,
|
services.send_notifications(us,
|
||||||
history=history_delete,
|
history=history_delete)
|
||||||
users={member1.user, member2.user})
|
|
||||||
# Tasks
|
# Tasks
|
||||||
task = f.TaskFactory.create()
|
task = f.TaskFactory.create()
|
||||||
|
take_snapshot(task)
|
||||||
services.send_notifications(task,
|
services.send_notifications(task,
|
||||||
history=history_create,
|
history=history_create)
|
||||||
users={member1.user, member2.user})
|
|
||||||
|
|
||||||
services.send_notifications(task,
|
services.send_notifications(task,
|
||||||
history=history_change,
|
history=history_change)
|
||||||
users={member1.user, member2.user})
|
|
||||||
|
|
||||||
services.send_notifications(task,
|
services.send_notifications(task,
|
||||||
history=history_delete,
|
history=history_delete)
|
||||||
users={member1.user, member2.user})
|
|
||||||
|
|
||||||
# Wiki pages
|
# Wiki pages
|
||||||
wiki = f.WikiPageFactory.create()
|
wiki = f.WikiPageFactory.create()
|
||||||
|
take_snapshot(wiki)
|
||||||
services.send_notifications(wiki,
|
services.send_notifications(wiki,
|
||||||
history=history_create,
|
history=history_create)
|
||||||
users={member1.user, member2.user})
|
|
||||||
|
|
||||||
services.send_notifications(wiki,
|
services.send_notifications(wiki,
|
||||||
history=history_change,
|
history=history_change)
|
||||||
users={member1.user, member2.user})
|
|
||||||
|
|
||||||
services.send_notifications(wiki,
|
services.send_notifications(wiki,
|
||||||
history=history_delete,
|
history=history_delete)
|
||||||
users={member1.user, member2.user})
|
|
||||||
|
|
||||||
assert len(mail.outbox) == 24
|
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
def test_resource_notification_test(client, mail):
|
||||||
user1 = f.UserFactory.create()
|
user1 = f.UserFactory.create()
|
||||||
user2 = f.UserFactory.create()
|
user2 = f.UserFactory.create()
|
||||||
|
@ -242,13 +242,23 @@ def test_resource_notification_test(client, mail):
|
||||||
with patch(mock_path) as m:
|
with patch(mock_path) as m:
|
||||||
data = {"subject": "Fooooo", "version": issue.version}
|
data = {"subject": "Fooooo", "version": issue.version}
|
||||||
response = client.patch(url, json.dumps(data), content_type="application/json")
|
response = client.patch(url, json.dumps(data), content_type="application/json")
|
||||||
assert len(mail.outbox) == 1
|
|
||||||
assert response.status_code == 200
|
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:
|
with patch(mock_path) as m:
|
||||||
response = client.delete(url)
|
response = client.delete(url)
|
||||||
assert response.status_code == 204
|
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 len(mail.outbox) == 2
|
||||||
|
assert models.HistoryChangeNotification.objects.count() == 0
|
||||||
|
|
||||||
|
|
||||||
def test_watchers_assignation_for_issue(client):
|
def test_watchers_assignation_for_issue(client):
|
||||||
|
|
Loading…
Reference in New Issue