Merge pull request #451 from taigaio/refactoring-watchers-for-projects
Refactoring watchers for projectsremotes/origin/logger
commit
a3d996bf6b
|
@ -245,7 +245,7 @@ class WatcheableObjectModelSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
def save_watchers(self):
|
def save_watchers(self):
|
||||||
new_watcher_emails = set(self._watchers)
|
new_watcher_emails = set(self._watchers)
|
||||||
old_watcher_emails = set(notifications_services.get_watchers(self.object).values_list("email", flat=True))
|
old_watcher_emails = set(self.object.get_watchers().values_list("email", flat=True))
|
||||||
adding_watcher_emails = list(new_watcher_emails.difference(old_watcher_emails))
|
adding_watcher_emails = list(new_watcher_emails.difference(old_watcher_emails))
|
||||||
removing_watcher_emails = list(old_watcher_emails.difference(new_watcher_emails))
|
removing_watcher_emails = list(old_watcher_emails.difference(new_watcher_emails))
|
||||||
|
|
||||||
|
@ -259,11 +259,11 @@ class WatcheableObjectModelSerializer(serializers.ModelSerializer):
|
||||||
for user in removing_users:
|
for user in removing_users:
|
||||||
notifications_services.remove_watcher(self.object, user)
|
notifications_services.remove_watcher(self.object, user)
|
||||||
|
|
||||||
self.object.watchers = notifications_services.get_watchers(self.object)
|
self.object.watchers = self.object.get_watchers()
|
||||||
|
|
||||||
def to_native(self, obj):
|
def to_native(self, obj):
|
||||||
ret = super(WatcheableObjectModelSerializer, self).to_native(obj)
|
ret = super(WatcheableObjectModelSerializer, self).to_native(obj)
|
||||||
ret["watchers"] = [user.email for user in notifications_services.get_watchers(obj)]
|
ret["watchers"] = [user.email for user in obj.get_watchers()]
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,12 @@ from taiga.base.utils.slug import slugify_uniquely
|
||||||
|
|
||||||
from taiga.projects.history.mixins import HistoryResourceMixin
|
from taiga.projects.history.mixins import HistoryResourceMixin
|
||||||
from taiga.projects.notifications.mixins import WatchedResourceMixin, WatchersViewSetMixin
|
from taiga.projects.notifications.mixins import WatchedResourceMixin, WatchersViewSetMixin
|
||||||
from taiga.projects.notifications.services import set_notify_policy, attach_notify_level_to_project_queryset
|
from taiga.projects.notifications.choices import NotifyLevel
|
||||||
|
from taiga.projects.notifications.utils import (
|
||||||
|
attach_project_watchers_attrs_to_queryset,
|
||||||
|
attach_project_is_watched_to_queryset,
|
||||||
|
attach_notify_level_to_project_queryset)
|
||||||
|
|
||||||
from taiga.projects.mixins.ordering import BulkUpdateOrderMixin
|
from taiga.projects.mixins.ordering import BulkUpdateOrderMixin
|
||||||
from taiga.projects.mixins.on_destroy import MoveOnDestroyMixin
|
from taiga.projects.mixins.on_destroy import MoveOnDestroyMixin
|
||||||
|
|
||||||
|
@ -48,10 +53,11 @@ from . import services
|
||||||
|
|
||||||
from .votes.mixins.viewsets import LikedResourceMixin, VotersViewSetMixin
|
from .votes.mixins.viewsets import LikedResourceMixin, VotersViewSetMixin
|
||||||
|
|
||||||
|
|
||||||
######################################################
|
######################################################
|
||||||
## Project
|
## Project
|
||||||
######################################################
|
######################################################
|
||||||
class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet):
|
class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin, ModelCrudViewSet):
|
||||||
queryset = models.Project.objects.all()
|
queryset = models.Project.objects.all()
|
||||||
serializer_class = serializers.ProjectDetailSerializer
|
serializer_class = serializers.ProjectDetailSerializer
|
||||||
admin_serializer_class = serializers.ProjectDetailAdminSerializer
|
admin_serializer_class = serializers.ProjectDetailAdminSerializer
|
||||||
|
@ -64,19 +70,28 @@ class ProjectViewSet(LikedResourceMixin, HistoryResourceMixin, WatchedResourceMi
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = super().get_queryset()
|
qs = super().get_queryset()
|
||||||
qs = self.attach_votes_attrs_to_queryset(qs)
|
qs = self.attach_votes_attrs_to_queryset(qs)
|
||||||
qs = self.attach_watchers_attrs_to_queryset(qs)
|
qs = attach_project_watchers_attrs_to_queryset(qs)
|
||||||
qs = attach_notify_level_to_project_queryset(qs, self.request.user)
|
if self.request.user.is_authenticated():
|
||||||
|
qs = attach_project_is_watched_to_queryset(qs, self.request.user)
|
||||||
|
qs = attach_notify_level_to_project_queryset(qs, self.request.user)
|
||||||
|
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
@detail_route(methods=["POST"])
|
@detail_route(methods=["POST"])
|
||||||
def watch(self, request, pk=None):
|
def watch(self, request, pk=None):
|
||||||
response = super(ProjectViewSet, self).watch(request, pk)
|
project = self.get_object()
|
||||||
notify_policy = self.get_object().notify_policies.get(user=request.user)
|
self.check_permissions(request, "watch", project)
|
||||||
level = request.DATA.get("notify_level", None)
|
notify_level = request.DATA.get("notify_level", NotifyLevel.watch)
|
||||||
if level is not None:
|
project.add_watcher(self.request.user, notify_level=notify_level)
|
||||||
set_notify_policy(notify_policy, level)
|
return response.Ok()
|
||||||
|
|
||||||
return response
|
@detail_route(methods=["POST"])
|
||||||
|
def unwatch(self, request, pk=None):
|
||||||
|
project = self.get_object()
|
||||||
|
self.check_permissions(request, "unwatch", project)
|
||||||
|
user = self.request.user
|
||||||
|
project.remove_watcher(user)
|
||||||
|
return response.Ok()
|
||||||
|
|
||||||
@list_route(methods=["POST"])
|
@list_route(methods=["POST"])
|
||||||
def bulk_update_order(self, request, **kwargs):
|
def bulk_update_order(self, request, **kwargs):
|
||||||
|
|
|
@ -20,7 +20,7 @@ import uuid
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import signals
|
from django.db.models import signals, Q
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
@ -38,9 +38,14 @@ from taiga.base.utils.dicts import dict_sum
|
||||||
from taiga.base.utils.sequence import arithmetic_progression
|
from taiga.base.utils.sequence import arithmetic_progression
|
||||||
from taiga.base.utils.slug import slugify_uniquely_for_queryset
|
from taiga.base.utils.slug import slugify_uniquely_for_queryset
|
||||||
|
|
||||||
from . import choices
|
from taiga.projects.notifications.choices import NotifyLevel
|
||||||
|
from taiga.projects.notifications.services import (
|
||||||
|
get_notify_policy,
|
||||||
|
set_notify_policy_level,
|
||||||
|
set_notify_policy_level_to_ignore,
|
||||||
|
create_notify_policy_if_not_exists)
|
||||||
|
|
||||||
from . notifications.mixins import WatchedModelMixin
|
from . import choices
|
||||||
|
|
||||||
|
|
||||||
class Membership(models.Model):
|
class Membership(models.Model):
|
||||||
|
@ -73,6 +78,10 @@ class Membership(models.Model):
|
||||||
user_order = models.IntegerField(default=10000, null=False, blank=False,
|
user_order = models.IntegerField(default=10000, null=False, blank=False,
|
||||||
verbose_name=_("user order"))
|
verbose_name=_("user order"))
|
||||||
|
|
||||||
|
def get_related_people(self):
|
||||||
|
related_people = get_user_model().objects.filter(id=self.user.id)
|
||||||
|
return related_people
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
# TODO: Review and do it more robust
|
# TODO: Review and do it more robust
|
||||||
memberships = Membership.objects.filter(user=self.user, project=self.project)
|
memberships = Membership.objects.filter(user=self.user, project=self.project)
|
||||||
|
@ -120,7 +129,7 @@ class ProjectDefaults(models.Model):
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
class Project(ProjectDefaults, WatchedModelMixin, TaggedMixin, models.Model):
|
class Project(ProjectDefaults, TaggedMixin, models.Model):
|
||||||
name = models.CharField(max_length=250, null=False, blank=False,
|
name = models.CharField(max_length=250, null=False, blank=False,
|
||||||
verbose_name=_("name"))
|
verbose_name=_("name"))
|
||||||
slug = models.SlugField(max_length=250, unique=True, null=False, blank=True,
|
slug = models.SlugField(max_length=250, unique=True, null=False, blank=True,
|
||||||
|
@ -334,6 +343,39 @@ class Project(ProjectDefaults, WatchedModelMixin, TaggedMixin, models.Model):
|
||||||
"assigned": self._get_user_stories_points(assigned_user_stories),
|
"assigned": self._get_user_stories_points(assigned_user_stories),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _get_q_watchers(self):
|
||||||
|
return Q(notify_policies__project_id=self.id) & ~Q(notify_policies__notify_level=NotifyLevel.ignore)
|
||||||
|
|
||||||
|
def get_watchers(self):
|
||||||
|
return get_user_model().objects.filter(self._get_q_watchers())
|
||||||
|
|
||||||
|
def get_related_people(self):
|
||||||
|
related_people_q = Q()
|
||||||
|
|
||||||
|
## - Owner
|
||||||
|
if self.owner_id:
|
||||||
|
related_people_q.add(Q(id=self.owner_id), Q.OR)
|
||||||
|
|
||||||
|
## - Watchers
|
||||||
|
related_people_q.add(self._get_q_watchers(), Q.OR)
|
||||||
|
|
||||||
|
## - Apply filters
|
||||||
|
related_people = get_user_model().objects.filter(related_people_q)
|
||||||
|
|
||||||
|
## - Exclude inactive and system users and remove duplicate
|
||||||
|
related_people = related_people.exclude(is_active=False)
|
||||||
|
related_people = related_people.exclude(is_system=True)
|
||||||
|
related_people = related_people.distinct()
|
||||||
|
return related_people
|
||||||
|
|
||||||
|
def add_watcher(self, user, notify_level=NotifyLevel.watch):
|
||||||
|
notify_policy = create_notify_policy_if_not_exists(self, user)
|
||||||
|
set_notify_policy_level(notify_policy, notify_level)
|
||||||
|
|
||||||
|
def remove_watcher(self, user):
|
||||||
|
notify_policy = get_notify_policy(self, user)
|
||||||
|
set_notify_policy_level_to_ignore(notify_policy)
|
||||||
|
|
||||||
|
|
||||||
class ProjectModulesConfig(models.Model):
|
class ProjectModulesConfig(models.Model):
|
||||||
project = models.OneToOneField("Project", null=False, blank=False,
|
project = models.OneToOneField("Project", null=False, blank=False,
|
||||||
|
|
|
@ -33,12 +33,9 @@ class NotifyPolicyViewSet(ModelCrudViewSet):
|
||||||
permission_classes = (permissions.NotifyPolicyPermission,)
|
permission_classes = (permissions.NotifyPolicyPermission,)
|
||||||
|
|
||||||
def _build_needed_notify_policies(self):
|
def _build_needed_notify_policies(self):
|
||||||
watched_project_ids = user_services.get_watched_content_for_user(self.request.user).get("project", [])
|
|
||||||
|
|
||||||
projects = Project.objects.filter(
|
projects = Project.objects.filter(
|
||||||
Q(owner=self.request.user) |
|
Q(owner=self.request.user) |
|
||||||
Q(memberships__user=self.request.user) |
|
Q(memberships__user=self.request.user)
|
||||||
Q(id__in=watched_project_ids)
|
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
for project in projects:
|
for project in projects:
|
||||||
|
@ -50,13 +47,6 @@ class NotifyPolicyViewSet(ModelCrudViewSet):
|
||||||
|
|
||||||
self._build_needed_notify_policies()
|
self._build_needed_notify_policies()
|
||||||
|
|
||||||
# With really want to include the policies related to any content:
|
return models.NotifyPolicy.objects.filter(user=self.request.user).filter(
|
||||||
# - The user is the owner of the project
|
Q(project__owner=self.request.user) | Q(project__memberships__user=self.request.user)
|
||||||
# - The user is member of the project
|
|
||||||
# - The user is watching the project
|
|
||||||
watched_project_ids = user_services.get_watched_content_for_user(self.request.user).get("project", [])
|
|
||||||
|
|
||||||
return models.NotifyPolicy.objects.filter(user=self.request.user).filter(Q(project__owner=self.request.user) |
|
|
||||||
Q(project__memberships__user=self.request.user) |
|
|
||||||
Q(project__id__in=watched_project_ids)
|
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
|
@ -51,7 +51,7 @@ class WatchedResourceMixin:
|
||||||
def attach_watchers_attrs_to_queryset(self, queryset):
|
def attach_watchers_attrs_to_queryset(self, queryset):
|
||||||
qs = attach_watchers_to_queryset(queryset)
|
qs = attach_watchers_to_queryset(queryset)
|
||||||
if self.request.user.is_authenticated():
|
if self.request.user.is_authenticated():
|
||||||
qs = attach_is_watched_to_queryset(self.request.user, qs)
|
qs = attach_is_watched_to_queryset(qs, self.request.user)
|
||||||
|
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
@ -126,21 +126,19 @@ class WatchedModelMixin(object):
|
||||||
"""
|
"""
|
||||||
return self.project
|
return self.project
|
||||||
|
|
||||||
def get_watchers(self) -> frozenset:
|
def get_watchers(self) -> object:
|
||||||
"""
|
"""
|
||||||
Default implementation method for obtain a list of
|
Default implementation method for obtain a list of
|
||||||
watchers for current instance.
|
watchers for current instance.
|
||||||
|
|
||||||
NOTE: the default implementation returns frozen
|
|
||||||
set of all watchers if "watchers" attribute exists
|
|
||||||
in a model.
|
|
||||||
|
|
||||||
WARNING: it returns a full evaluated set and in
|
|
||||||
future, for project with 1000k watchers it can be
|
|
||||||
very inefficient way for obtain watchers but at
|
|
||||||
this momment is the simplest way.
|
|
||||||
"""
|
"""
|
||||||
return frozenset(services.get_watchers(self))
|
return services.get_watchers(self)
|
||||||
|
|
||||||
|
def get_related_people(self) -> object:
|
||||||
|
"""
|
||||||
|
Default implementation for obtain the related people of
|
||||||
|
current instance.
|
||||||
|
"""
|
||||||
|
return services.get_related_people(self)
|
||||||
|
|
||||||
def get_watched(self, user_or_id):
|
def get_watched(self, user_or_id):
|
||||||
return services.get_watched(user_or_id, type(self))
|
return services.get_watched(user_or_id, type(self))
|
||||||
|
@ -194,7 +192,7 @@ class WatchedResourceModelSerializer(serializers.ModelSerializer):
|
||||||
instance = super(WatchedResourceModelSerializer, self).restore_object(attrs, instance)
|
instance = super(WatchedResourceModelSerializer, self).restore_object(attrs, instance)
|
||||||
if instance is not None and self.validate_watchers(attrs, "watchers"):
|
if instance is not None and self.validate_watchers(attrs, "watchers"):
|
||||||
new_watcher_ids = set(attrs.get("watchers", []))
|
new_watcher_ids = set(attrs.get("watchers", []))
|
||||||
old_watcher_ids = set(services.get_watchers(instance).values_list("id", flat=True))
|
old_watcher_ids = set(instance.get_watchers().values_list("id", flat=True))
|
||||||
adding_watcher_ids = list(new_watcher_ids.difference(old_watcher_ids))
|
adding_watcher_ids = list(new_watcher_ids.difference(old_watcher_ids))
|
||||||
removing_watcher_ids = list(old_watcher_ids.difference(new_watcher_ids))
|
removing_watcher_ids = list(old_watcher_ids.difference(new_watcher_ids))
|
||||||
|
|
||||||
|
@ -207,7 +205,7 @@ class WatchedResourceModelSerializer(serializers.ModelSerializer):
|
||||||
for user in removing_users:
|
for user in removing_users:
|
||||||
services.remove_watcher(instance, user)
|
services.remove_watcher(instance, user)
|
||||||
|
|
||||||
instance.watchers = services.get_watchers(instance)
|
instance.watchers = instance.get_watchers()
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
@ -215,7 +213,7 @@ class WatchedResourceModelSerializer(serializers.ModelSerializer):
|
||||||
def to_native(self, obj):
|
def to_native(self, obj):
|
||||||
#watchers is wasn't attached via the get_queryset of the viewset we need to manually add it
|
#watchers is wasn't attached via the get_queryset of the viewset we need to manually add it
|
||||||
if obj is not None and not hasattr(obj, "watchers"):
|
if obj is not None and not hasattr(obj, "watchers"):
|
||||||
obj.watchers = [user.id for user in services.get_watchers(obj)]
|
obj.watchers = [user.id for user in obj.get_watchers()]
|
||||||
|
|
||||||
return super(WatchedResourceModelSerializer, self).to_native(obj)
|
return super(WatchedResourceModelSerializer, self).to_native(obj)
|
||||||
|
|
||||||
|
@ -235,7 +233,7 @@ class WatchersViewSetMixin:
|
||||||
self.check_permissions(request, 'retrieve', resource)
|
self.check_permissions(request, 'retrieve', resource)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.object = services.get_watchers(resource).get(pk=pk)
|
self.object = resource.get_watchers().get(pk=pk)
|
||||||
except ObjectDoesNotExist: # or User.DoesNotExist
|
except ObjectDoesNotExist: # or User.DoesNotExist
|
||||||
return response.NotFound()
|
return response.NotFound()
|
||||||
|
|
||||||
|
@ -252,4 +250,4 @@ class WatchersViewSetMixin:
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
resource = self.resource_model.objects.get(pk=self.kwargs.get("resource_id"))
|
resource = self.resource_model.objects.get(pk=self.kwargs.get("resource_id"))
|
||||||
return services.get_watchers(resource)
|
return resource.get_watchers()
|
||||||
|
|
|
@ -21,6 +21,7 @@ from functools import partial
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.db.transaction import atomic
|
from django.db.transaction import atomic
|
||||||
from django.db import IntegrityError, transaction
|
from django.db import IntegrityError, transaction
|
||||||
|
from django.db.models import Q
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -30,7 +31,6 @@ from django.utils.translation import ugettext as _
|
||||||
from djmail import template_mail
|
from djmail import template_mail
|
||||||
|
|
||||||
from taiga.base import exceptions as exc
|
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.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,
|
from taiga.projects.history.services import (make_key_from_model_object,
|
||||||
|
@ -90,31 +90,6 @@ def get_notify_policy(project, user):
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
def attach_notify_level_to_project_queryset(queryset, user):
|
|
||||||
"""
|
|
||||||
Function that attach "notify_level" attribute on each queryset
|
|
||||||
result for query notification level of current user for each
|
|
||||||
project in the most efficient way.
|
|
||||||
|
|
||||||
:param queryset: A Django queryset object.
|
|
||||||
:param user: A User model object.
|
|
||||||
|
|
||||||
:return: Queryset object with the additional `as_field` field.
|
|
||||||
"""
|
|
||||||
user_id = getattr(user, "id", None) or "NULL"
|
|
||||||
default_level = NotifyLevel.notwatch
|
|
||||||
|
|
||||||
sql = strip_lines("""
|
|
||||||
COALESCE((SELECT notifications_notifypolicy.notify_level
|
|
||||||
FROM notifications_notifypolicy
|
|
||||||
WHERE notifications_notifypolicy.project_id = projects_project.id
|
|
||||||
AND notifications_notifypolicy.user_id = {user_id}),
|
|
||||||
{default_level})
|
|
||||||
""")
|
|
||||||
sql = sql.format(user_id=user_id, default_level=default_level)
|
|
||||||
return queryset.extra(select={"notify_level": sql})
|
|
||||||
|
|
||||||
|
|
||||||
def analize_object_for_watchers(obj:object, history:object):
|
def analize_object_for_watchers(obj:object, history:object):
|
||||||
"""
|
"""
|
||||||
Generic implementation for analize model objects and
|
Generic implementation for analize model objects and
|
||||||
|
@ -182,9 +157,6 @@ def get_users_to_notify(obj, *, discard_users=None) -> list:
|
||||||
candidates.update(filter(_can_notify_light, obj.project.get_watchers()))
|
candidates.update(filter(_can_notify_light, obj.project.get_watchers()))
|
||||||
candidates.update(filter(_can_notify_light, obj.get_participants()))
|
candidates.update(filter(_can_notify_light, obj.get_participants()))
|
||||||
|
|
||||||
#TODO: coger los watchers del proyecto que quieren ser notificados por correo
|
|
||||||
#Filtrar los watchers según su nivel de watched y su nivel en el proyecto
|
|
||||||
|
|
||||||
# Remove the changer from candidates
|
# Remove the changer from candidates
|
||||||
if discard_users:
|
if discard_users:
|
||||||
candidates = candidates - set(discard_users)
|
candidates = candidates - set(discard_users)
|
||||||
|
@ -320,15 +292,49 @@ def process_sync_notifications():
|
||||||
send_sync_notifications(notification.pk)
|
send_sync_notifications(notification.pk)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_q_watchers(obj):
|
||||||
|
obj_type = apps.get_model("contenttypes", "ContentType").objects.get_for_model(obj)
|
||||||
|
return Q(watched__content_type=obj_type, watched__object_id=obj.id)
|
||||||
|
|
||||||
|
|
||||||
def get_watchers(obj):
|
def get_watchers(obj):
|
||||||
"""Get the watchers of an object.
|
"""Get the watchers of an object.
|
||||||
|
|
||||||
:param obj: Any Django model instance.
|
:param obj: Any Django model instance.
|
||||||
|
|
||||||
:return: User queryset object representing the users that voted the object.
|
:return: User queryset object representing the users that watch the object.
|
||||||
"""
|
"""
|
||||||
obj_type = apps.get_model("contenttypes", "ContentType").objects.get_for_model(obj)
|
return get_user_model().objects.filter(_get_q_watchers(obj))
|
||||||
return get_user_model().objects.filter(watched__content_type=obj_type, watched__object_id=obj.id)
|
|
||||||
|
|
||||||
|
def get_related_people(obj):
|
||||||
|
"""Get the related people of an object for notifications.
|
||||||
|
|
||||||
|
:param obj: Any Django model instance.
|
||||||
|
|
||||||
|
:return: User queryset object representing the users related to the object.
|
||||||
|
"""
|
||||||
|
related_people_q = Q()
|
||||||
|
|
||||||
|
## - Owner
|
||||||
|
if hasattr(obj, "owner_id") and obj.owner_id:
|
||||||
|
related_people_q.add(Q(id=obj.owner_id), Q.OR)
|
||||||
|
|
||||||
|
## - Assigned to
|
||||||
|
if hasattr(obj, "assigned_to_id") and obj.assigned_to_id:
|
||||||
|
related_people_q.add(Q(id=obj.assigned_to_id), Q.OR)
|
||||||
|
|
||||||
|
## - Watchers
|
||||||
|
related_people_q.add(_get_q_watchers(obj), Q.OR)
|
||||||
|
|
||||||
|
## - Apply filters
|
||||||
|
related_people = get_user_model().objects.filter(related_people_q)
|
||||||
|
|
||||||
|
## - Exclude inactive and system users and remove duplicate
|
||||||
|
related_people = related_people.exclude(is_active=False)
|
||||||
|
related_people = related_people.exclude(is_system=True)
|
||||||
|
related_people = related_people.distinct()
|
||||||
|
return related_people
|
||||||
|
|
||||||
|
|
||||||
def get_watched(user_or_id, model):
|
def get_watched(user_or_id, model):
|
||||||
|
@ -389,7 +395,7 @@ def remove_watcher(obj, user):
|
||||||
qs.delete()
|
qs.delete()
|
||||||
|
|
||||||
|
|
||||||
def set_notify_policy(notify_policy, notify_level):
|
def set_notify_policy_level(notify_policy, notify_level):
|
||||||
"""
|
"""
|
||||||
Set notification level for specified policy.
|
Set notification level for specified policy.
|
||||||
"""
|
"""
|
||||||
|
@ -400,6 +406,13 @@ def set_notify_policy(notify_policy, notify_level):
|
||||||
notify_policy.save()
|
notify_policy.save()
|
||||||
|
|
||||||
|
|
||||||
|
def set_notify_policy_level_to_ignore(notify_policy):
|
||||||
|
"""
|
||||||
|
Set notification level for specified policy.
|
||||||
|
"""
|
||||||
|
set_notify_policy_level(notify_policy, NotifyLevel.ignore)
|
||||||
|
|
||||||
|
|
||||||
def make_ms_thread_index(msg_id, dt):
|
def make_ms_thread_index(msg_id, dt):
|
||||||
"""
|
"""
|
||||||
Create the 22-byte base of the thread-index string in the format:
|
Create the 22-byte base of the thread-index string in the format:
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
from .choices import NotifyLevel
|
||||||
|
from taiga.base.utils.text import strip_lines
|
||||||
|
|
||||||
def attach_watchers_to_queryset(queryset, as_field="watchers"):
|
def attach_watchers_to_queryset(queryset, as_field="watchers"):
|
||||||
"""Attach watching user ids to each object of the queryset.
|
"""Attach watching user ids to each object of the queryset.
|
||||||
|
@ -39,7 +40,7 @@ def attach_watchers_to_queryset(queryset, as_field="watchers"):
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
def attach_is_watched_to_queryset(user, queryset, as_field="is_watched"):
|
def attach_is_watched_to_queryset(queryset, user, as_field="is_watched"):
|
||||||
"""Attach is_watched boolean to each object of the queryset.
|
"""Attach is_watched boolean to each object of the queryset.
|
||||||
|
|
||||||
:param user: A users.User object model
|
:param user: A users.User object model
|
||||||
|
@ -61,3 +62,74 @@ def attach_is_watched_to_queryset(user, queryset, as_field="is_watched"):
|
||||||
sql = sql.format(type_id=type.id, tbl=model._meta.db_table, user_id=user.id)
|
sql = sql.format(type_id=type.id, tbl=model._meta.db_table, user_id=user.id)
|
||||||
qs = queryset.extra(select={as_field: sql})
|
qs = queryset.extra(select={as_field: sql})
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
def attach_project_is_watched_to_queryset(queryset, user, as_field="is_watched"):
|
||||||
|
"""Attach is_watched boolean to each object of the projects queryset.
|
||||||
|
|
||||||
|
:param user: A users.User object model
|
||||||
|
:param queryset: A Django projects queryset object.
|
||||||
|
:param as_field: Attach the boolean as an attribute with this name.
|
||||||
|
|
||||||
|
:return: Queryset object with the additional `as_field` field.
|
||||||
|
"""
|
||||||
|
model = queryset.model
|
||||||
|
type = apps.get_model("contenttypes", "ContentType").objects.get_for_model(model)
|
||||||
|
sql = ("""SELECT CASE WHEN (SELECT count(*)
|
||||||
|
FROM notifications_notifypolicy
|
||||||
|
WHERE notifications_notifypolicy.project_id = {tbl}.id
|
||||||
|
AND notifications_notifypolicy.user_id = {user_id}
|
||||||
|
AND notifications_notifypolicy.notify_level != {ignore_notify_level}) > 0
|
||||||
|
|
||||||
|
THEN TRUE
|
||||||
|
ELSE FALSE
|
||||||
|
END""")
|
||||||
|
sql = sql.format(tbl=model._meta.db_table, user_id=user.id, ignore_notify_level=NotifyLevel.ignore)
|
||||||
|
qs = queryset.extra(select={as_field: sql})
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
def attach_project_watchers_attrs_to_queryset(queryset, as_field="watchers"):
|
||||||
|
"""Attach watching user ids to each project of the queryset.
|
||||||
|
|
||||||
|
:param queryset: A Django projects queryset object.
|
||||||
|
:param as_field: Attach the watchers as an attribute with this name.
|
||||||
|
|
||||||
|
:return: Queryset object with the additional `as_field` field.
|
||||||
|
"""
|
||||||
|
model = queryset.model
|
||||||
|
type = apps.get_model("contenttypes", "ContentType").objects.get_for_model(model)
|
||||||
|
|
||||||
|
sql = ("""SELECT array(SELECT user_id
|
||||||
|
FROM notifications_notifypolicy
|
||||||
|
WHERE notifications_notifypolicy.project_id = {tbl}.id
|
||||||
|
AND notifications_notifypolicy.notify_level != {ignore_notify_level})""")
|
||||||
|
sql = sql.format(tbl=model._meta.db_table, ignore_notify_level=NotifyLevel.ignore)
|
||||||
|
qs = queryset.extra(select={as_field: sql})
|
||||||
|
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
|
def attach_notify_level_to_project_queryset(queryset, user):
|
||||||
|
"""
|
||||||
|
Function that attach "notify_level" attribute on each queryset
|
||||||
|
result for query notification level of current user for each
|
||||||
|
project in the most efficient way.
|
||||||
|
|
||||||
|
:param queryset: A Django queryset object.
|
||||||
|
:param user: A User model object.
|
||||||
|
|
||||||
|
:return: Queryset object with the additional `as_field` field.
|
||||||
|
"""
|
||||||
|
user_id = getattr(user, "id", None) or "NULL"
|
||||||
|
default_level = NotifyLevel.notwatch
|
||||||
|
|
||||||
|
sql = strip_lines("""
|
||||||
|
COALESCE((SELECT notifications_notifypolicy.notify_level
|
||||||
|
FROM notifications_notifypolicy
|
||||||
|
WHERE notifications_notifypolicy.project_id = projects_project.id
|
||||||
|
AND notifications_notifypolicy.user_id = {user_id}),
|
||||||
|
{default_level})
|
||||||
|
""")
|
||||||
|
sql = sql.format(user_id=user_id, default_level=default_level)
|
||||||
|
return queryset.extra(select={"notify_level": sql})
|
||||||
|
|
|
@ -45,31 +45,12 @@ def _push_to_timelines(project, user, obj, event_type, created_datetime, extra_d
|
||||||
namespace=build_project_namespace(project),
|
namespace=build_project_namespace(project),
|
||||||
extra_data=extra_data)
|
extra_data=extra_data)
|
||||||
|
|
||||||
## User profile timelines
|
if hasattr(obj, "get_related_people"):
|
||||||
## - Me
|
related_people = obj.get_related_people()
|
||||||
related_people = User.objects.filter(id=user.id)
|
|
||||||
|
|
||||||
## - Owner
|
_push_to_timeline(related_people, obj, event_type, created_datetime,
|
||||||
if hasattr(obj, "owner_id") and obj.owner_id:
|
namespace=build_user_namespace(user),
|
||||||
related_people |= User.objects.filter(id=obj.owner_id)
|
extra_data=extra_data)
|
||||||
|
|
||||||
## - Assigned to
|
|
||||||
if hasattr(obj, "assigned_to_id") and obj.assigned_to_id:
|
|
||||||
related_people |= User.objects.filter(id=obj.assigned_to_id)
|
|
||||||
|
|
||||||
## - Watchers
|
|
||||||
watchers = notifications_services.get_watchers(obj)
|
|
||||||
if watchers:
|
|
||||||
related_people |= watchers
|
|
||||||
|
|
||||||
## - Exclude inactive and system users and remove duplicate
|
|
||||||
related_people = related_people.exclude(is_active=False)
|
|
||||||
related_people = related_people.exclude(is_system=True)
|
|
||||||
related_people = related_people.distinct()
|
|
||||||
|
|
||||||
_push_to_timeline(related_people, obj, event_type, created_datetime,
|
|
||||||
namespace=build_user_namespace(user),
|
|
||||||
extra_data=extra_data)
|
|
||||||
else:
|
else:
|
||||||
# Actions not related with a project
|
# Actions not related with a project
|
||||||
## - Me
|
## - Me
|
||||||
|
|
|
@ -22,6 +22,7 @@ from django.apps import apps
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
from easy_thumbnails.files import get_thumbnailer
|
from easy_thumbnails.files import get_thumbnailer
|
||||||
|
@ -29,6 +30,7 @@ from easy_thumbnails.exceptions import InvalidImageFormatError
|
||||||
|
|
||||||
from taiga.base import exceptions as exc
|
from taiga.base import exceptions as exc
|
||||||
from taiga.base.utils.urls import get_absolute_url
|
from taiga.base.utils.urls import get_absolute_url
|
||||||
|
from taiga.projects.notifications.choices import NotifyLevel
|
||||||
|
|
||||||
from .gravatar import get_gravatar_url
|
from .gravatar import get_gravatar_url
|
||||||
|
|
||||||
|
@ -179,6 +181,50 @@ def get_watched_content_for_user(user):
|
||||||
return user_watches
|
return user_watches
|
||||||
|
|
||||||
|
|
||||||
|
def _build_favourites_sql_for_projects(for_user):
|
||||||
|
sql = """
|
||||||
|
SELECT projects_project.id AS id, null AS ref, 'project' AS type, 'watch' AS action,
|
||||||
|
tags, notifications_notifypolicy.project_id AS object_id, projects_project.id AS project,
|
||||||
|
slug AS slug, projects_project.name AS subject,
|
||||||
|
notifications_notifypolicy.created_at, coalesce(watchers, 0) as total_watchers, coalesce(votes_votes.count, 0) total_votes, null AS assigned_to
|
||||||
|
FROM notifications_notifypolicy
|
||||||
|
INNER JOIN projects_project
|
||||||
|
ON (projects_project.id = notifications_notifypolicy.project_id)
|
||||||
|
LEFT JOIN (SELECT project_id, count(*) watchers
|
||||||
|
FROM notifications_notifypolicy
|
||||||
|
WHERE notifications_notifypolicy.notify_level != {ignore_notify_level}
|
||||||
|
GROUP BY project_id
|
||||||
|
) type_watchers
|
||||||
|
ON projects_project.id = type_watchers.project_id
|
||||||
|
LEFT JOIN votes_votes
|
||||||
|
ON (projects_project.id = votes_votes.object_id AND {project_content_type_id} = votes_votes.content_type_id)
|
||||||
|
WHERE notifications_notifypolicy.user_id = {for_user_id}
|
||||||
|
UNION
|
||||||
|
SELECT projects_project.id AS id, null AS ref, 'project' AS type, 'vote' AS action,
|
||||||
|
tags, votes_vote.object_id AS object_id, projects_project.id AS project,
|
||||||
|
slug AS slug, projects_project.name AS subject,
|
||||||
|
votes_vote.created_date, coalesce(watchers, 0) as total_watchers, coalesce(votes_votes.count, 0) total_votes, null AS assigned_to
|
||||||
|
FROM votes_vote
|
||||||
|
INNER JOIN projects_project
|
||||||
|
ON (projects_project.id = votes_vote.object_id)
|
||||||
|
LEFT JOIN (SELECT project_id, count(*) watchers
|
||||||
|
FROM notifications_notifypolicy
|
||||||
|
WHERE notifications_notifypolicy.notify_level != {ignore_notify_level}
|
||||||
|
GROUP BY project_id
|
||||||
|
) type_watchers
|
||||||
|
ON projects_project.id = type_watchers.project_id
|
||||||
|
LEFT JOIN votes_votes
|
||||||
|
ON (projects_project.id = votes_votes.object_id AND {project_content_type_id} = votes_votes.content_type_id)
|
||||||
|
WHERE votes_vote.user_id = {for_user_id}
|
||||||
|
"""
|
||||||
|
sql = sql.format(
|
||||||
|
for_user_id=for_user.id,
|
||||||
|
ignore_notify_level=NotifyLevel.ignore,
|
||||||
|
project_content_type_id=ContentType.objects.get(app_label="projects", model="project").id)
|
||||||
|
return sql
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _build_favourites_sql_for_type(for_user, type, table_name, ref_column="ref",
|
def _build_favourites_sql_for_type(for_user, type, table_name, ref_column="ref",
|
||||||
project_column="project_id", assigned_to_column="assigned_to_id",
|
project_column="project_id", assigned_to_column="assigned_to_id",
|
||||||
slug_column="slug", subject_column="subject"):
|
slug_column="slug", subject_column="subject"):
|
||||||
|
@ -217,6 +263,7 @@ def _build_favourites_sql_for_type(for_user, type, table_name, ref_column="ref",
|
||||||
ref_column = ref_column, project_column=project_column,
|
ref_column = ref_column, project_column=project_column,
|
||||||
assigned_to_column=assigned_to_column, slug_column=slug_column,
|
assigned_to_column=assigned_to_column, slug_column=slug_column,
|
||||||
subject_column=subject_column)
|
subject_column=subject_column)
|
||||||
|
|
||||||
return sql
|
return sql
|
||||||
|
|
||||||
|
|
||||||
|
@ -298,12 +345,7 @@ def get_favourites_list(for_user, from_user, type=None, action=None, q=None):
|
||||||
userstories_sql=_build_favourites_sql_for_type(for_user, "userstory", "userstories_userstory", slug_column="null"),
|
userstories_sql=_build_favourites_sql_for_type(for_user, "userstory", "userstories_userstory", slug_column="null"),
|
||||||
tasks_sql=_build_favourites_sql_for_type(for_user, "task", "tasks_task", slug_column="null"),
|
tasks_sql=_build_favourites_sql_for_type(for_user, "task", "tasks_task", slug_column="null"),
|
||||||
issues_sql=_build_favourites_sql_for_type(for_user, "issue", "issues_issue", slug_column="null"),
|
issues_sql=_build_favourites_sql_for_type(for_user, "issue", "issues_issue", slug_column="null"),
|
||||||
projects_sql=_build_favourites_sql_for_type(for_user, "project", "projects_project",
|
projects_sql=_build_favourites_sql_for_projects(for_user))
|
||||||
ref_column="null",
|
|
||||||
project_column="id",
|
|
||||||
assigned_to_column="null",
|
|
||||||
subject_column="projects_project.name")
|
|
||||||
)
|
|
||||||
|
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute(sql)
|
cursor.execute(sql)
|
||||||
|
|
|
@ -81,13 +81,6 @@ def data():
|
||||||
f.VotesFactory(content_type=project_ct, object_id=m.private_project1.pk, count=2)
|
f.VotesFactory(content_type=project_ct, object_id=m.private_project1.pk, count=2)
|
||||||
f.VotesFactory(content_type=project_ct, object_id=m.private_project2.pk, count=2)
|
f.VotesFactory(content_type=project_ct, object_id=m.private_project2.pk, count=2)
|
||||||
|
|
||||||
f.WatchedFactory(content_type=project_ct, object_id=m.public_project.pk, user=m.project_member_with_perms)
|
|
||||||
f.WatchedFactory(content_type=project_ct, object_id=m.public_project.pk, user=m.project_owner)
|
|
||||||
f.WatchedFactory(content_type=project_ct, object_id=m.private_project1.pk, user=m.project_member_with_perms)
|
|
||||||
f.WatchedFactory(content_type=project_ct, object_id=m.private_project1.pk, user=m.project_owner)
|
|
||||||
f.WatchedFactory(content_type=project_ct, object_id=m.private_project2.pk, user=m.project_member_with_perms)
|
|
||||||
f.WatchedFactory(content_type=project_ct, object_id=m.private_project2.pk, user=m.project_owner)
|
|
||||||
|
|
||||||
return m
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
@ -322,11 +315,11 @@ def test_project_watchers_list(client, data):
|
||||||
]
|
]
|
||||||
|
|
||||||
results = helper_test_http_method_and_count(client, 'get', public_url, None, users)
|
results = helper_test_http_method_and_count(client, 'get', public_url, None, users)
|
||||||
assert results == [(200, 2), (200, 2), (200, 2), (200, 2), (200, 2)]
|
assert results == [(200, 1), (200, 1), (200, 1), (200, 1), (200, 1)]
|
||||||
results = helper_test_http_method_and_count(client, 'get', private1_url, None, users)
|
results = helper_test_http_method_and_count(client, 'get', private1_url, None, users)
|
||||||
assert results == [(200, 2), (200, 2), (200, 2), (200, 2), (200, 2)]
|
assert results == [(200, 3), (200, 3), (200, 3), (200, 3), (200, 3)]
|
||||||
results = helper_test_http_method_and_count(client, 'get', private2_url, None, users)
|
results = helper_test_http_method_and_count(client, 'get', private2_url, None, users)
|
||||||
assert results == [(401, 0), (403, 0), (403, 0), (200, 2), (200, 2)]
|
assert results == [(401, 0), (403, 0), (403, 0), (200, 3), (200, 3)]
|
||||||
|
|
||||||
|
|
||||||
def test_project_watchers_retrieve(client, data):
|
def test_project_watchers_retrieve(client, data):
|
||||||
|
|
|
@ -68,7 +68,7 @@ def test_valid_project_import_without_extra_data(client):
|
||||||
]
|
]
|
||||||
assert all(map(lambda x: len(response_data[x]) == 0, must_empty_children))
|
assert all(map(lambda x: len(response_data[x]) == 0, must_empty_children))
|
||||||
assert response_data["owner"] == user.email
|
assert response_data["owner"] == user.email
|
||||||
assert response_data["watchers"] == [user_watching.email]
|
assert response_data["watchers"] == [user.email, user_watching.email]
|
||||||
|
|
||||||
|
|
||||||
def test_valid_project_import_with_not_existing_memberships(client):
|
def test_valid_project_import_with_not_existing_memberships(client):
|
||||||
|
|
|
@ -32,6 +32,7 @@ from .. import factories as f
|
||||||
|
|
||||||
from taiga.base.utils import json
|
from taiga.base.utils import json
|
||||||
from taiga.projects.notifications import services
|
from taiga.projects.notifications import services
|
||||||
|
from taiga.projects.notifications import utils
|
||||||
from taiga.projects.notifications import models
|
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
|
||||||
|
@ -56,7 +57,7 @@ def test_attach_notify_level_to_project_queryset():
|
||||||
f.ProjectFactory.create()
|
f.ProjectFactory.create()
|
||||||
|
|
||||||
qs = project1.__class__.objects.order_by("id")
|
qs = project1.__class__.objects.order_by("id")
|
||||||
qs = services.attach_notify_level_to_project_queryset(qs, project1.owner)
|
qs = utils.attach_notify_level_to_project_queryset(qs, project1.owner)
|
||||||
|
|
||||||
assert len(qs) == 2
|
assert len(qs) == 2
|
||||||
assert qs[0].notify_level == NotifyLevel.notwatch
|
assert qs[0].notify_level == NotifyLevel.notwatch
|
||||||
|
@ -64,7 +65,7 @@ def test_attach_notify_level_to_project_queryset():
|
||||||
|
|
||||||
services.create_notify_policy(project1, project1.owner, NotifyLevel.watch)
|
services.create_notify_policy(project1, project1.owner, NotifyLevel.watch)
|
||||||
qs = project1.__class__.objects.order_by("id")
|
qs = project1.__class__.objects.order_by("id")
|
||||||
qs = services.attach_notify_level_to_project_queryset(qs, project1.owner)
|
qs = utils.attach_notify_level_to_project_queryset(qs, project1.owner)
|
||||||
assert qs[0].notify_level == NotifyLevel.watch
|
assert qs[0].notify_level == NotifyLevel.watch
|
||||||
assert qs[1].notify_level == NotifyLevel.notwatch
|
assert qs[1].notify_level == NotifyLevel.notwatch
|
||||||
|
|
||||||
|
@ -143,10 +144,25 @@ def test_users_to_notify():
|
||||||
role2 = f.RoleFactory.create(project=project, permissions=[])
|
role2 = f.RoleFactory.create(project=project, permissions=[])
|
||||||
|
|
||||||
member1 = f.MembershipFactory.create(project=project, role=role1)
|
member1 = f.MembershipFactory.create(project=project, role=role1)
|
||||||
|
policy_member1 = member1.user.notify_policies.get(project=project)
|
||||||
|
policy_member1.notify_level = NotifyLevel.ignore
|
||||||
|
policy_member1.save()
|
||||||
member2 = f.MembershipFactory.create(project=project, role=role1)
|
member2 = f.MembershipFactory.create(project=project, role=role1)
|
||||||
|
policy_member2 = member2.user.notify_policies.get(project=project)
|
||||||
|
policy_member2.notify_level = NotifyLevel.ignore
|
||||||
|
policy_member2.save()
|
||||||
member3 = f.MembershipFactory.create(project=project, role=role1)
|
member3 = f.MembershipFactory.create(project=project, role=role1)
|
||||||
|
policy_member3 = member3.user.notify_policies.get(project=project)
|
||||||
|
policy_member3.notify_level = NotifyLevel.ignore
|
||||||
|
policy_member3.save()
|
||||||
member4 = f.MembershipFactory.create(project=project, role=role1)
|
member4 = f.MembershipFactory.create(project=project, role=role1)
|
||||||
|
policy_member4 = member4.user.notify_policies.get(project=project)
|
||||||
|
policy_member4.notify_level = NotifyLevel.ignore
|
||||||
|
policy_member4.save()
|
||||||
member5 = f.MembershipFactory.create(project=project, role=role2)
|
member5 = f.MembershipFactory.create(project=project, role=role2)
|
||||||
|
policy_member5 = member5.user.notify_policies.get(project=project)
|
||||||
|
policy_member5.notify_level = NotifyLevel.ignore
|
||||||
|
policy_member5.save()
|
||||||
inactive_member1 = f.MembershipFactory.create(project=project, role=role1)
|
inactive_member1 = f.MembershipFactory.create(project=project, role=role1)
|
||||||
inactive_member1.user.is_active = False
|
inactive_member1.user.is_active = False
|
||||||
inactive_member1.user.save()
|
inactive_member1.user.save()
|
||||||
|
@ -158,14 +174,13 @@ def test_users_to_notify():
|
||||||
|
|
||||||
policy_model_cls = apps.get_model("notifications", "NotifyPolicy")
|
policy_model_cls = apps.get_model("notifications", "NotifyPolicy")
|
||||||
|
|
||||||
policy1 = policy_model_cls.objects.get(user=member1.user)
|
policy_inactive_member1 = policy_model_cls.objects.get(user=inactive_member1.user)
|
||||||
policy2 = policy_model_cls.objects.get(user=member3.user)
|
policy_inactive_member1.notify_level = NotifyLevel.watch
|
||||||
policy3 = policy_model_cls.objects.get(user=inactive_member1.user)
|
policy_inactive_member1.save()
|
||||||
policy3.notify_level = NotifyLevel.watch
|
|
||||||
policy3.save()
|
policy_system_member1 = policy_model_cls.objects.get(user=system_member1.user)
|
||||||
policy4 = policy_model_cls.objects.get(user=system_member1.user)
|
policy_system_member1.notify_level = NotifyLevel.watch
|
||||||
policy4.notify_level = NotifyLevel.watch
|
policy_system_member1.save()
|
||||||
policy4.save()
|
|
||||||
|
|
||||||
history = MagicMock()
|
history = MagicMock()
|
||||||
history.owner = member2.user
|
history.owner = member2.user
|
||||||
|
@ -174,13 +189,15 @@ def test_users_to_notify():
|
||||||
# Test basic description modifications
|
# Test basic description modifications
|
||||||
issue.description = "test1"
|
issue.description = "test1"
|
||||||
issue.save()
|
issue.save()
|
||||||
|
policy_member4.notify_level = NotifyLevel.watch
|
||||||
|
policy_member4.save()
|
||||||
users = services.get_users_to_notify(issue)
|
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()
|
||||||
|
|
||||||
# Test watch notify level in one member
|
# Test watch notify level in one member
|
||||||
policy1.notify_level = NotifyLevel.watch
|
policy_member1.notify_level = NotifyLevel.watch
|
||||||
policy1.save()
|
policy_member1.save()
|
||||||
|
|
||||||
users = services.get_users_to_notify(issue)
|
users = services.get_users_to_notify(issue)
|
||||||
assert len(users) == 2
|
assert len(users) == 2
|
||||||
|
@ -188,13 +205,15 @@ def test_users_to_notify():
|
||||||
|
|
||||||
# Test with watchers
|
# Test with watchers
|
||||||
issue.add_watcher(member3.user)
|
issue.add_watcher(member3.user)
|
||||||
|
policy_member3.notify_level = NotifyLevel.watch
|
||||||
|
policy_member3.save()
|
||||||
users = services.get_users_to_notify(issue)
|
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()}
|
||||||
|
|
||||||
# Test with watchers with ignore policy
|
# Test with watchers with ignore policy
|
||||||
policy2.notify_level = NotifyLevel.ignore
|
policy_member3.notify_level = NotifyLevel.ignore
|
||||||
policy2.save()
|
policy_member3.save()
|
||||||
|
|
||||||
issue.add_watcher(member3.user)
|
issue.add_watcher(member3.user)
|
||||||
users = services.get_users_to_notify(issue)
|
users = services.get_users_to_notify(issue)
|
||||||
|
|
|
@ -200,6 +200,7 @@ def test_update_project_timeline():
|
||||||
project = factories.ProjectFactory.create(name="test project timeline")
|
project = factories.ProjectFactory.create(name="test project timeline")
|
||||||
history_services.take_snapshot(project, user=project.owner)
|
history_services.take_snapshot(project, user=project.owner)
|
||||||
project.add_watcher(user_watcher)
|
project.add_watcher(user_watcher)
|
||||||
|
print("PPPP")
|
||||||
project.name = "test project timeline updated"
|
project.name = "test project timeline updated"
|
||||||
project.save()
|
project.save()
|
||||||
history_services.take_snapshot(project, user=project.owner)
|
history_services.take_snapshot(project, user=project.owner)
|
||||||
|
@ -341,7 +342,7 @@ def test_update_membership_timeline():
|
||||||
def test_delete_project_timeline():
|
def test_delete_project_timeline():
|
||||||
project = factories.ProjectFactory.create(name="test project timeline")
|
project = factories.ProjectFactory.create(name="test project timeline")
|
||||||
user_watcher= factories.UserFactory()
|
user_watcher= factories.UserFactory()
|
||||||
project.add_watcher(user_watcher)
|
project.add_watcher(user_watcher)
|
||||||
history_services.take_snapshot(project, user=project.owner, delete=True)
|
history_services.take_snapshot(project, user=project.owner, delete=True)
|
||||||
user_timeline = service.get_project_timeline(project)
|
user_timeline = service.get_project_timeline(project)
|
||||||
assert user_timeline[0].event_type == "projects.project.delete"
|
assert user_timeline[0].event_type == "projects.project.delete"
|
||||||
|
@ -354,7 +355,7 @@ def test_delete_project_timeline():
|
||||||
def test_delete_milestone_timeline():
|
def test_delete_milestone_timeline():
|
||||||
milestone = factories.MilestoneFactory.create(name="test milestone timeline")
|
milestone = factories.MilestoneFactory.create(name="test milestone timeline")
|
||||||
user_watcher= factories.UserFactory()
|
user_watcher= factories.UserFactory()
|
||||||
milestone.add_watcher(user_watcher)
|
milestone.add_watcher(user_watcher)
|
||||||
history_services.take_snapshot(milestone, user=milestone.owner, delete=True)
|
history_services.take_snapshot(milestone, user=milestone.owner, delete=True)
|
||||||
project_timeline = service.get_project_timeline(milestone.project)
|
project_timeline = service.get_project_timeline(milestone.project)
|
||||||
assert project_timeline[0].event_type == "milestones.milestone.delete"
|
assert project_timeline[0].event_type == "milestones.milestone.delete"
|
||||||
|
@ -367,7 +368,7 @@ def test_delete_milestone_timeline():
|
||||||
def test_delete_user_story_timeline():
|
def test_delete_user_story_timeline():
|
||||||
user_story = factories.UserStoryFactory.create(subject="test us timeline")
|
user_story = factories.UserStoryFactory.create(subject="test us timeline")
|
||||||
user_watcher= factories.UserFactory()
|
user_watcher= factories.UserFactory()
|
||||||
user_story.add_watcher(user_watcher)
|
user_story.add_watcher(user_watcher)
|
||||||
history_services.take_snapshot(user_story, user=user_story.owner, delete=True)
|
history_services.take_snapshot(user_story, user=user_story.owner, delete=True)
|
||||||
project_timeline = service.get_project_timeline(user_story.project)
|
project_timeline = service.get_project_timeline(user_story.project)
|
||||||
assert project_timeline[0].event_type == "userstories.userstory.delete"
|
assert project_timeline[0].event_type == "userstories.userstory.delete"
|
||||||
|
@ -380,7 +381,7 @@ def test_delete_user_story_timeline():
|
||||||
def test_delete_issue_timeline():
|
def test_delete_issue_timeline():
|
||||||
issue = factories.IssueFactory.create(subject="test issue timeline")
|
issue = factories.IssueFactory.create(subject="test issue timeline")
|
||||||
user_watcher= factories.UserFactory()
|
user_watcher= factories.UserFactory()
|
||||||
issue.add_watcher(user_watcher)
|
issue.add_watcher(user_watcher)
|
||||||
history_services.take_snapshot(issue, user=issue.owner, delete=True)
|
history_services.take_snapshot(issue, user=issue.owner, delete=True)
|
||||||
project_timeline = service.get_project_timeline(issue.project)
|
project_timeline = service.get_project_timeline(issue.project)
|
||||||
assert project_timeline[0].event_type == "issues.issue.delete"
|
assert project_timeline[0].event_type == "issues.issue.delete"
|
||||||
|
@ -393,7 +394,7 @@ def test_delete_issue_timeline():
|
||||||
def test_delete_task_timeline():
|
def test_delete_task_timeline():
|
||||||
task = factories.TaskFactory.create(subject="test task timeline")
|
task = factories.TaskFactory.create(subject="test task timeline")
|
||||||
user_watcher= factories.UserFactory()
|
user_watcher= factories.UserFactory()
|
||||||
task.add_watcher(user_watcher)
|
task.add_watcher(user_watcher)
|
||||||
history_services.take_snapshot(task, user=task.owner, delete=True)
|
history_services.take_snapshot(task, user=task.owner, delete=True)
|
||||||
project_timeline = service.get_project_timeline(task.project)
|
project_timeline = service.get_project_timeline(task.project)
|
||||||
assert project_timeline[0].event_type == "tasks.task.delete"
|
assert project_timeline[0].event_type == "tasks.task.delete"
|
||||||
|
@ -406,7 +407,7 @@ def test_delete_task_timeline():
|
||||||
def test_delete_wiki_page_timeline():
|
def test_delete_wiki_page_timeline():
|
||||||
page = factories.WikiPageFactory.create(slug="test wiki page timeline")
|
page = factories.WikiPageFactory.create(slug="test wiki page timeline")
|
||||||
user_watcher= factories.UserFactory()
|
user_watcher= factories.UserFactory()
|
||||||
page.add_watcher(user_watcher)
|
page.add_watcher(user_watcher)
|
||||||
history_services.take_snapshot(page, user=page.owner, delete=True)
|
history_services.take_snapshot(page, user=page.owner, delete=True)
|
||||||
project_timeline = service.get_project_timeline(page.project)
|
project_timeline = service.get_project_timeline(page.project)
|
||||||
assert project_timeline[0].event_type == "wiki.wikipage.delete"
|
assert project_timeline[0].event_type == "wiki.wikipage.delete"
|
||||||
|
|
|
@ -19,6 +19,8 @@ import pytest
|
||||||
import json
|
import json
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
|
||||||
|
|
||||||
from .. import factories as f
|
from .. import factories as f
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
pytestmark = pytest.mark.django_db
|
||||||
|
@ -124,8 +126,10 @@ def test_get_project_watchers(client):
|
||||||
|
|
||||||
def test_get_project_is_watched(client):
|
def test_get_project_is_watched(client):
|
||||||
user = f.UserFactory.create()
|
user = f.UserFactory.create()
|
||||||
project = f.create_project(owner=user)
|
project = f.ProjectFactory.create(is_private=False,
|
||||||
f.MembershipFactory.create(project=project, user=user, is_owner=True)
|
anon_permissions=list(map(lambda x: x[0], ANON_PERMISSIONS)),
|
||||||
|
public_permissions=list(map(lambda x: x[0], USER_PERMISSIONS)))
|
||||||
|
|
||||||
url_detail = reverse("projects-detail", args=(project.id,))
|
url_detail = reverse("projects-detail", args=(project.id,))
|
||||||
url_watch = reverse("projects-watch", args=(project.id,))
|
url_watch = reverse("projects-watch", args=(project.id,))
|
||||||
url_unwatch = reverse("projects-unwatch", args=(project.id,))
|
url_unwatch = reverse("projects-unwatch", args=(project.id,))
|
||||||
|
@ -133,6 +137,7 @@ def test_get_project_is_watched(client):
|
||||||
client.login(user)
|
client.login(user)
|
||||||
|
|
||||||
response = client.get(url_detail)
|
response = client.get(url_detail)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.data['watchers'] == []
|
assert response.data['watchers'] == []
|
||||||
assert response.data['is_watched'] == False
|
assert response.data['is_watched'] == False
|
||||||
|
|
Loading…
Reference in New Issue