Adding votes and watched projects to sample_data

remotes/origin/enhancement/email-actions
Alejandro Alonso 2015-08-18 09:23:10 +02:00 committed by David Barragán Merino
parent f3641f5cfb
commit bccdc2fae1
50 changed files with 1205 additions and 134 deletions

View File

@ -31,7 +31,7 @@ from taiga.base.api.utils import get_object_or_404
from taiga.base.utils.slug import slugify_uniquely
from taiga.projects.history.mixins import HistoryResourceMixin
from taiga.projects.notifications.mixins import WatchedResourceMixin
from taiga.projects.notifications.mixins import WatchedResourceMixin, WatchersViewSetMixin
from taiga.projects.mixins.ordering import BulkUpdateOrderMixin
from taiga.projects.mixins.on_destroy import MoveOnDestroyMixin
@ -270,6 +270,10 @@ class ProjectFansViewSet(VotersViewSetMixin, ModelListViewSet):
resource_model = models.Project
class ProjectWatchersViewSet(WatchersViewSetMixin, ModelListViewSet):
permission_classes = (permissions.ProjectWatchersPermission,)
resource_model = models.Project
######################################################
## Custom values for selectors
######################################################

View File

@ -27,7 +27,7 @@ from taiga.base.api.utils import get_object_or_404
from taiga.users.models import User
from taiga.projects.notifications.mixins import WatchedResourceMixin
from taiga.projects.notifications.mixins import WatchedResourceMixin, WatchersViewSetMixin
from taiga.projects.occ import OCCResourceMixin
from taiga.projects.history.mixins import HistoryResourceMixin
@ -243,3 +243,8 @@ class IssueViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, W
class IssueVotersViewSet(VotersViewSetMixin, ModelListViewSet):
permission_classes = (permissions.IssueVotersPermission,)
resource_model = models.Issue
class IssueWatchersViewSet(WatchersViewSetMixin, ModelListViewSet):
permission_classes = (permissions.IssueWatchersPermission,)
resource_model = models.Issue

View File

@ -1,17 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import connection
from django.db import models, migrations
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.management import update_all_contenttypes
def create_notifications(apps, schema_editor):
update_all_contenttypes()
migrations.RunSQL(sql="""
INSERT INTO notifications_watched (object_id, created_date, content_type_id, user_id)
SELECT issue_id AS object_id, now() AS created_date, {content_type_id} AS content_type_id, user_id
FROM issues_issue_watchers""".format(content_type_id=ContentType.objects.get(model='issue').id))
update_all_contenttypes()
sql="""
INSERT INTO notifications_watched (object_id, created_date, content_type_id, user_id, project_id)
SELECT issue_id AS object_id, now() AS created_date, {content_type_id} AS content_type_id, user_id, project_id
FROM issues_issue_watchers INNER JOIN issues_issue ON issues_issue_watchers.issue_id = issues_issue.id""".format(content_type_id=ContentType.objects.get(model='issue').id)
cursor = connection.cursor()
cursor.execute(sql)
class Migration(migrations.Migration):

View File

@ -52,3 +52,10 @@ class IssueVotersPermission(TaigaResourcePermission):
global_perms = None
retrieve_perms = HasProjectPerm('view_issues')
list_perms = HasProjectPerm('view_issues')
class IssueWatchersPermission(TaigaResourcePermission):
enought_perms = IsProjectOwner() | IsSuperUser()
global_perms = None
retrieve_perms = HasProjectPerm('view_issues')
list_perms = HasProjectPerm('view_issues')

View File

@ -27,7 +27,7 @@ from taiga.base.utils import db, text
from taiga.projects.issues.apps import (
connect_issues_signals,
disconnect_issues_signals)
from taiga.projects.votes import services as votes_services
from . import models
@ -84,7 +84,8 @@ def issues_to_csv(project, queryset):
fieldnames = ["ref", "subject", "description", "milestone", "owner",
"owner_full_name", "assigned_to", "assigned_to_full_name",
"status", "severity", "priority", "type", "is_closed",
"attachments", "external_reference", "tags"]
"attachments", "external_reference", "tags",
"watchers", "voters"]
for custom_attr in project.issuecustomattributes.all():
fieldnames.append(custom_attr.name)
@ -108,6 +109,8 @@ def issues_to_csv(project, queryset):
"attachments": issue.attachments.count(),
"external_reference": issue.external_reference,
"tags": ",".join(issue.tags or []),
"watchers": [u.id for u in issue.get_watchers()],
"voters": votes_services.get_voters(issue).count(),
}
for custom_attr in project.issuecustomattributes.all():

View File

@ -37,6 +37,7 @@ from taiga.projects.wiki.models import *
from taiga.projects.attachments.models import *
from taiga.projects.custom_attributes.models import *
from taiga.projects.history.services import take_snapshot
from taiga.projects.votes.services import add_vote
from taiga.events.apps import disconnect_events_signals
@ -97,7 +98,8 @@ NUM_TASKS = getattr(settings, "SAMPLE_DATA_NUM_TASKS", (0, 4))
NUM_USS_BACK = getattr(settings, "SAMPLE_DATA_NUM_USS_BACK", (8, 20))
NUM_ISSUES = getattr(settings, "SAMPLE_DATA_NUM_ISSUES", (12, 25))
NUM_ATTACHMENTS = getattr(settings, "SAMPLE_DATA_NUM_ATTACHMENTS", (0, 4))
NUM_VOTES = getattr(settings, "SAMPLE_DATA_NUM_VOTES", (0, 3))
NUM_PROJECT_WATCHERS = getattr(settings, "SAMPLE_DATA_NUM_PROJECT_WATCHERS", (0, 3))
class Command(BaseCommand):
sd = SampleDataHelper(seed=12345678901)
@ -215,6 +217,7 @@ class Command(BaseCommand):
project.total_story_points = int(defined_points * self.sd.int(5,12) / 10)
project.save()
self.create_votes(project, project)
def create_attachment(self, obj, order):
attached_file = self.sd.file_from_directory(*ATTACHMENT_SAMPLE_DATA)
@ -287,7 +290,7 @@ class Command(BaseCommand):
bug.save()
watching_user = self.sd.db_object_from_queryset(project.memberships.filter(user__isnull=False)).user
bug.watchers.add(watching_user)
bug.add_watcher(watching_user)
take_snapshot(bug,
comment=self.sd.paragraph(),
@ -300,6 +303,7 @@ class Command(BaseCommand):
comment=self.sd.paragraph(),
user=bug.owner)
self.create_votes(bug, project)
return bug
def create_task(self, project, milestone, us, min_date, max_date, closed=False):
@ -338,7 +342,7 @@ class Command(BaseCommand):
user=task.owner)
watching_user = self.sd.db_object_from_queryset(project.memberships.filter(user__isnull=False)).user
task.watchers.add(watching_user)
task.add_watcher(watching_user)
# Add history entry
task.status=self.sd.db_object_from_queryset(project.task_statuses.all())
@ -347,6 +351,7 @@ class Command(BaseCommand):
comment=self.sd.paragraph(),
user=task.owner)
self.create_votes(task, project)
return task
def create_us(self, project, milestone=None, computable_project_roles=[]):
@ -387,7 +392,7 @@ class Command(BaseCommand):
us.save()
watching_user = self.sd.db_object_from_queryset(project.memberships.filter(user__isnull=False)).user
us.watchers.add(watching_user)
us.add_watcher(watching_user)
take_snapshot(us,
comment=self.sd.paragraph(),
@ -400,6 +405,7 @@ class Command(BaseCommand):
comment=self.sd.paragraph(),
user=us.owner)
self.create_votes(us, project)
return us
def create_milestone(self, project, start_date, end_date):
@ -434,6 +440,11 @@ class Command(BaseCommand):
project.is_kanban_activated = True
project.save()
take_snapshot(project, user=project.owner)
for i in range(self.sd.int(*NUM_PROJECT_WATCHERS)):
watching_user = self.sd.db_object_from_queryset(User.objects.all())
project.add_watcher(watching_user)
return project
def create_user(self, counter=None, username=None, full_name=None, email=None):
@ -452,3 +463,8 @@ class Command(BaseCommand):
user.save()
return user
def create_votes(self, obj, project):
for i in range(self.sd.int(*NUM_VOTES)):
voting_user=self.sd.db_object_from_queryset(project.members.all())
add_vote(obj, voting_user)

View File

@ -14,12 +14,6 @@ class Migration(migrations.Migration):
]
operations = [
migrations.AddField(
model_name='project',
name='watchers',
field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL, related_name='projects_project+', null=True, verbose_name='watchers'),
preserve_default=True,
),
migrations.AlterField(
model_name='project',
name='public_permissions',

View File

@ -1,28 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.management import update_all_contenttypes
def create_notifications(apps, schema_editor):
update_all_contenttypes()
migrations.RunSQL(sql="""
INSERT INTO notifications_watched (object_id, created_date, content_type_id, user_id)
SELECT project_id AS object_id, now() AS created_date, {content_type_id} AS content_type_id, user_id
FROM projects_project_watchers""".format(content_type_id=ContentType.objects.get(model='project').id))
class Migration(migrations.Migration):
dependencies = [
('notifications', '0004_watched'),
('projects', '0024_auto_20150810_1247'),
]
operations = [
migrations.RunPython(create_notifications),
migrations.RemoveField(
model_name='project',
name='watchers',
),
]

View File

@ -17,10 +17,10 @@
from taiga.base import filters
from taiga.base import response
from taiga.base.decorators import detail_route
from taiga.base.api import ModelCrudViewSet
from taiga.base.api import ModelCrudViewSet, ModelListViewSet
from taiga.base.api.utils import get_object_or_404
from taiga.projects.notifications.mixins import WatchedResourceMixin
from taiga.projects.notifications.mixins import WatchedResourceMixin, WatchersViewSetMixin
from taiga.projects.history.mixins import HistoryResourceMixin
@ -36,9 +36,11 @@ class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudView
permission_classes = (permissions.MilestonePermission,)
filter_backends = (filters.CanViewMilestonesFilterBackend,)
filter_fields = ("project", "closed")
queryset = models.Milestone.objects.all()
def get_queryset(self):
qs = models.Milestone.objects.all()
qs = super().get_queryset()
qs = self.attach_watchers_attrs_to_queryset(qs)
qs = qs.prefetch_related("user_stories",
"user_stories__role_points",
"user_stories__role_points__points",
@ -91,3 +93,8 @@ class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudView
optimal_points -= optimal_points_per_day
return response.Ok(milestone_stats)
class MilestoneWatchersViewSet(WatchersViewSetMixin, ModelListViewSet):
permission_classes = (permissions.MilestoneWatchersPermission,)
resource_model = models.Milestone

View File

@ -1,16 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import connection
from django.db import models, migrations
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.management import update_all_contenttypes
def create_notifications(apps, schema_editor):
update_all_contenttypes()
migrations.RunSQL(sql="""
INSERT INTO notifications_watched (object_id, created_date, content_type_id, user_id)
SELECT milestone_id AS object_id, now() AS created_date, {content_type_id} AS content_type_id, user_id
FROM milestones_milestone_watchers""".format(content_type_id=ContentType.objects.get(model='milestone').id)),
update_all_contenttypes()
sql="""
INSERT INTO notifications_watched (object_id, created_date, content_type_id, user_id, project_id)
SELECT milestone_id AS object_id, now() AS created_date, {content_type_id} AS content_type_id, user_id, project_id
FROM milestones_milestone_watchers INNER JOIN milestones_milestone ON milestones_milestone_watchers.milestone_id = milestones_milestone.id""".format(content_type_id=ContentType.objects.get(model='milestone').id)
cursor = connection.cursor()
cursor.execute(sql)
class Migration(migrations.Migration):

View File

@ -15,8 +15,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.api.permissions import (TaigaResourcePermission, HasProjectPerm,
IsProjectOwner, AllowAny,
PermissionComponent, IsSuperUser)
IsAuthenticated, IsProjectOwner, AllowAny,
IsSuperUser)
class MilestonePermission(TaigaResourcePermission):
@ -29,3 +29,11 @@ class MilestonePermission(TaigaResourcePermission):
destroy_perms = HasProjectPerm('delete_milestone')
list_perms = AllowAny()
stats_perms = HasProjectPerm('view_milestones')
watch_perms = IsAuthenticated() & HasProjectPerm('view_milestones')
unwatch_perms = IsAuthenticated() & HasProjectPerm('view_milestones')
class MilestoneWatchersPermission(TaigaResourcePermission):
enought_perms = IsProjectOwner() | IsSuperUser()
global_perms = None
retrieve_perms = HasProjectPerm('view_milestones')
list_perms = HasProjectPerm('view_milestones')

View File

@ -17,7 +17,6 @@
from django.utils.translation import ugettext as _
from taiga.base.api import serializers
from taiga.base.utils import json
from taiga.projects.notifications.mixins import WatchedResourceModelSerializer
from taiga.projects.notifications.validators import WatchersValidator

View File

@ -19,8 +19,9 @@ from django.db.models import Q
from taiga.base.api import ModelCrudViewSet
from taiga.projects.notifications.choices import NotifyLevel
from taiga.projects.notifications.models import Watched
from taiga.projects.models import Project
from taiga.users import services as user_services
from . import serializers
from . import models
from . import permissions
@ -32,9 +33,13 @@ class NotifyPolicyViewSet(ModelCrudViewSet):
permission_classes = (permissions.NotifyPolicyPermission,)
def _build_needed_notify_policies(self):
watched_content = user_services.get_watched_content_for_user(self.request.user)
watched_content_project_ids = watched_content.values_list("project__id", flat=True).distinct()
projects = Project.objects.filter(
Q(owner=self.request.user) |
Q(memberships__user=self.request.user)
Q(memberships__user=self.request.user) |
Q(id__in=watched_content_project_ids)
).distinct()
for project in projects:
@ -45,5 +50,14 @@ class NotifyPolicyViewSet(ModelCrudViewSet):
return models.NotifyPolicy.objects.none()
self._build_needed_notify_policies()
qs = models.NotifyPolicy.objects.filter(user=self.request.user)
return qs.distinct()
# With really want to include the policies related to any content:
# - The user is the owner of the project
# - The user is member of the project
# - The user is watching any object from the project
watched_content = user_services.get_watched_content_for_user(self.request.user)
watched_content_project_ids = watched_content.values_list("project__id", flat=True).distinct()
return models.NotifyPolicy.objects.filter(Q(project__owner=self.request.user) |
Q(project__memberships__user=self.request.user) |
Q(project__id__in=watched_content_project_ids)
).distinct()

View File

@ -5,10 +5,6 @@ from django.db import models, migrations
from django.conf import settings
def fill_watched_table(apps, schema_editor):
Watched = apps.get_model("notifications", "Watched")
print("test")
class Migration(migrations.Migration):
dependencies = [
@ -21,15 +17,22 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Watched',
fields=[
('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID', auto_created=True)),
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('object_id', models.PositiveIntegerField()),
('created_date', models.DateTimeField(verbose_name='created date', auto_now_add=True)),
('content_type', models.ForeignKey(to='contenttypes.ContentType')),
('user', models.ForeignKey(related_name='watched', verbose_name='user', to=settings.AUTH_USER_MODEL)),
('project', models.ForeignKey(to='projects.Project', verbose_name='project', related_name='watched')),
],
options={
'verbose_name': 'Watched',
'verbose_name_plural': 'Watched',
},
bases=(models.Model,),
),
migrations.RunPython(fill_watched_table),
migrations.AlterUniqueTogether(
name='watched',
unique_together=set([('content_type', 'object_id', 'user', 'project')]),
),
]

View File

@ -19,17 +19,21 @@ from operator import is_not
from django.apps import apps
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.utils.translation import ugettext_lazy as _
from taiga.base import response
from taiga.base.decorators import detail_route
from taiga.base.api import serializers
from taiga.base.api.utils import get_object_or_404
from taiga.base.fields import WatchersField
from taiga.projects.notifications import services
from taiga.projects.notifications.utils import attach_watchers_to_queryset, attach_is_watched_to_queryset
from taiga.users.models import User
from . import models
from . serializers import WatcherSerializer
class WatchedResourceMixin:
@ -121,7 +125,6 @@ class WatchedModelMixin(object):
that should works in almost all cases.
"""
return self.project
t
def get_watchers(self) -> frozenset:
"""
@ -139,6 +142,9 @@ class WatchedModelMixin(object):
"""
return frozenset(services.get_watchers(self))
def get_watched(self, user_or_id):
return services.get_watched(user_or_id, type(self))
def add_watcher(self, user):
services.add_watcher(self, user)
@ -209,7 +215,7 @@ class WatchedResourceModelSerializer(serializers.ModelSerializer):
def to_native(self, obj):
#watchers is wasn't attached via the get_queryset of the viewset we need to manually add it
if not hasattr(obj, "watchers"):
obj.watchers = services.get_watchers(obj)
obj.watchers = [user.id for user in services.get_watchers(obj)]
return super(WatchedResourceModelSerializer, self).to_native(obj)

View File

@ -22,7 +22,7 @@ from django.utils import timezone
from taiga.projects.history.choices import HISTORY_TYPE_CHOICES
from .choices import NOTIFY_LEVEL_CHOICES
from .choices import NOTIFY_LEVEL_CHOICES, NotifyLevel
class NotifyPolicy(models.Model):
@ -84,8 +84,9 @@ class Watched(models.Model):
related_name="watched", verbose_name=_("user"))
created_date = models.DateTimeField(auto_now_add=True, null=False, blank=False,
verbose_name=_("created date"))
project = models.ForeignKey("projects.Project", null=False, blank=False,
verbose_name=_("project"),related_name="watched")
class Meta:
verbose_name = _("Watched")
verbose_name_plural = _("Watched")
unique_together = ("content_type", "object_id", "user")
unique_together = ("content_type", "object_id", "user", "project")

View File

@ -17,9 +17,10 @@
import json
from taiga.base.api import serializers
from taiga.users.models import User
from . import models
from . import choices
class NotifyPolicySerializer(serializers.ModelSerializer):
@ -31,3 +32,11 @@ class NotifyPolicySerializer(serializers.ModelSerializer):
def get_project_name(self, obj):
return obj.project.name
class WatcherSerializer(serializers.ModelSerializer):
full_name = serializers.CharField(source='get_full_name', required=False)
class Meta:
model = User
fields = ('id', 'username', 'full_name')

View File

@ -20,6 +20,7 @@ from django.apps import apps
from django.db.transaction import atomic
from django.db import IntegrityError, transaction
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth import get_user_model
from django.utils import timezone
from django.conf import settings
from django.utils.translation import ugettext as _
@ -170,15 +171,19 @@ def get_users_to_notify(obj, *, discard_users=None) -> list:
candidates = set()
candidates.update(filter(_can_notify_hard, project.members.all()))
candidates.update(filter(_can_notify_light, obj.get_watchers()))
candidates.update(filter(_can_notify_light, obj.project.get_watchers()))
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
if discard_users:
candidates = candidates - set(discard_users)
candidates = filter(partial(_filter_by_permissions, obj), candidates)
candidates = set(filter(partial(_filter_by_permissions, obj), candidates))
# Filter disabled and system users
candidates = filter(partial(_filter_notificable), candidates)
candidates = set(filter(partial(_filter_notificable), candidates))
return frozenset(candidates)
@ -285,27 +290,54 @@ def process_sync_notifications():
def get_watchers(obj):
User = apps.get_model("users", "User")
Watched = apps.get_model("notifications", "Watched")
content_type = ContentType.objects.get_for_model(obj)
watching_user_ids = Watched.objects.filter(content_type=content_type, object_id=obj.id).values_list("user__id", flat=True)
return User.objects.filter(id__in=watching_user_ids)
"""Get the watchers of an object.
:param obj: Any Django model instance.
:return: User queryset object representing the users that voted the object.
"""
obj_type = apps.get_model("contenttypes", "ContentType").objects.get_for_model(obj)
return get_user_model().objects.filter(watched__content_type=obj_type, watched__object_id=obj.id)
def get_watched(user_or_id, model):
"""Get the objects watched by an user.
:param user_or_id: :class:`~taiga.users.models.User` instance or id.
:param model: Show only objects of this kind. Can be any Django model class.
:return: Queryset of objects representing the votes of the user.
"""
obj_type = apps.get_model("contenttypes", "ContentType").objects.get_for_model(model)
conditions = ('notifications_watched.content_type_id = %s',
'%s.id = notifications_watched.object_id' % model._meta.db_table,
'notifications_watched.user_id = %s')
if isinstance(user_or_id, get_user_model()):
user_id = user_or_id.id
else:
user_id = user_or_id
return model.objects.extra(where=conditions, tables=('notifications_watched',),
params=(obj_type.id, user_id))
def add_watcher(obj, user):
"""Add a watcher to an object.
If the user is already watching the object nothing happends, so this function can be considered
idempotent.
If the user is already watching the object nothing happents (except if there is a level update),
so this function can be considered idempotent.
:param obj: Any Django model instance.
:param user: User adding the watch. :class:`~taiga.users.models.User` instance.
"""
obj_type = apps.get_model("contenttypes", "ContentType").objects.get_for_model(obj)
with atomic():
watched, created = Watched.objects.get_or_create(content_type=obj_type, object_id=obj.id, user=user)
if not created:
return
watched, created = Watched.objects.get_or_create(content_type=obj_type,
object_id=obj.id, user=user, project=obj.project)
notify_policy, _ = apps.get_model("notifications", "NotifyPolicy").objects.get_or_create(
project=obj.project, user=user, defaults={"notify_level": NotifyLevel.watch})
return watched
@ -319,9 +351,8 @@ def remove_watcher(obj, user):
:param user: User removing the watch. :class:`~taiga.users.models.User` instance.
"""
obj_type = apps.get_model("contenttypes", "ContentType").objects.get_for_model(obj)
with atomic():
qs = Watched.objects.filter(content_type=obj_type, object_id=obj.id, user=user)
if not qs.exists():
return
qs = Watched.objects.filter(content_type=obj_type, object_id=obj.id, user=user)
if not qs.exists():
return
qs.delete()
qs.delete()

View File

@ -75,6 +75,13 @@ class ProjectFansPermission(TaigaResourcePermission):
list_perms = HasProjectPerm('view_project')
class ProjectWatchersPermission(TaigaResourcePermission):
enought_perms = IsProjectOwner() | IsSuperUser()
global_perms = None
retrieve_perms = HasProjectPerm('view_project')
list_perms = HasProjectPerm('view_project')
class MembershipPermission(TaigaResourcePermission):
retrieve_perms = HasProjectPerm('view_project')
create_perms = IsProjectOwner()

View File

@ -24,7 +24,7 @@ from taiga.base.api import ModelCrudViewSet, ModelListViewSet
from taiga.projects.models import Project, TaskStatus
from django.http import HttpResponse
from taiga.projects.notifications.mixins import WatchedResourceMixin
from taiga.projects.notifications.mixins import WatchedResourceMixin, WatchersViewSetMixin
from taiga.projects.history.mixins import HistoryResourceMixin
from taiga.projects.occ import OCCResourceMixin
from taiga.projects.votes.mixins.viewsets import VotedResourceMixin, VotersViewSetMixin
@ -177,3 +177,8 @@ class TaskViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixin, Wa
class TaskVotersViewSet(VotersViewSetMixin, ModelListViewSet):
permission_classes = (permissions.TaskVotersPermission,)
resource_model = models.Task
class TaskWatchersViewSet(WatchersViewSetMixin, ModelListViewSet):
permission_classes = (permissions.TaskWatchersPermission,)
resource_model = models.Task

View File

@ -1,16 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import connection
from django.db import models, migrations
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.management import update_all_contenttypes
def create_notifications(apps, schema_editor):
update_all_contenttypes()
migrations.RunSQL(sql="""
INSERT INTO notifications_watched (object_id, created_date, content_type_id, user_id)
SELECT task_id AS object_id, now() AS created_date, {content_type_id} AS content_type_id, user_id
FROM tasks_task_watchers""".format(content_type_id=ContentType.objects.get(model='task').id)),
update_all_contenttypes()
sql="""
INSERT INTO notifications_watched (object_id, created_date, content_type_id, user_id, project_id)
SELECT task_id AS object_id, now() AS created_date, {content_type_id} AS content_type_id, user_id, project_id
FROM tasks_task_watchers INNER JOIN tasks_task ON tasks_task_watchers.task_id = tasks_task.id""".format(content_type_id=ContentType.objects.get(model='task').id)
cursor = connection.cursor()
cursor.execute(sql)
class Migration(migrations.Migration):

View File

@ -42,3 +42,10 @@ class TaskVotersPermission(TaigaResourcePermission):
global_perms = None
retrieve_perms = HasProjectPerm('view_tasks')
list_perms = HasProjectPerm('view_tasks')
class TaskWatchersPermission(TaigaResourcePermission):
enought_perms = IsProjectOwner() | IsSuperUser()
global_perms = None
retrieve_perms = HasProjectPerm('view_tasks')
list_perms = HasProjectPerm('view_tasks')

View File

@ -23,6 +23,7 @@ from taiga.projects.tasks.apps import (
connect_tasks_signals,
disconnect_tasks_signals)
from taiga.events import events
from taiga.projects.votes import services as votes_services
from . import models
@ -95,7 +96,8 @@ def tasks_to_csv(project, queryset):
fieldnames = ["ref", "subject", "description", "user_story", "milestone", "owner",
"owner_full_name", "assigned_to", "assigned_to_full_name",
"status", "is_iocaine", "is_closed", "us_order",
"taskboard_order", "attachments", "external_reference", "tags"]
"taskboard_order", "attachments", "external_reference", "tags",
"watchers", "voters"]
for custom_attr in project.taskcustomattributes.all():
fieldnames.append(custom_attr.name)
@ -120,6 +122,8 @@ def tasks_to_csv(project, queryset):
"attachments": task.attachments.count(),
"external_reference": task.external_reference,
"tags": ",".join(task.tags or []),
"watchers": [u.id for u in task.get_watchers()],
"voters": votes_services.get_voters(task).count(),
}
for custom_attr in project.taskcustomattributes.all():
value = task.custom_attributes_values.attributes_values.get(str(custom_attr.id), None)

View File

@ -31,7 +31,7 @@ from taiga.base.decorators import list_route
from taiga.base.api import ModelCrudViewSet, ModelListViewSet
from taiga.base.api.utils import get_object_or_404
from taiga.projects.notifications.mixins import WatchedResourceMixin
from taiga.projects.notifications.mixins import WatchedResourceMixin, WatchersViewSetMixin
from taiga.projects.history.mixins import HistoryResourceMixin
from taiga.projects.occ import OCCResourceMixin
from taiga.projects.models import Project, UserStoryStatus
@ -270,3 +270,8 @@ class UserStoryViewSet(OCCResourceMixin, VotedResourceMixin, HistoryResourceMixi
class UserStoryVotersViewSet(VotersViewSetMixin, ModelListViewSet):
permission_classes = (permissions.UserStoryVotersPermission,)
resource_model = models.UserStory
class UserStoryWatchersViewSet(WatchersViewSetMixin, ModelListViewSet):
permission_classes = (permissions.UserStoryWatchersPermission,)
resource_model = models.UserStory

View File

@ -1,16 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import connection
from django.db import models, migrations
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.management import update_all_contenttypes
def create_notifications(apps, schema_editor):
update_all_contenttypes()
migrations.RunSQL(sql="""
INSERT INTO notifications_watched (object_id, created_date, content_type_id, user_id)
SELECT userstory_id AS object_id, now() AS created_date, {content_type_id} AS content_type_id, user_id
FROM userstories_userstory_watchers""".format(content_type_id=ContentType.objects.get(model='userstory').id)),
update_all_contenttypes()
sql="""
INSERT INTO notifications_watched (object_id, created_date, content_type_id, user_id, project_id)
SELECT userstory_id AS object_id, now() AS created_date, {content_type_id} AS content_type_id, user_id, project_id
FROM userstories_userstory_watchers INNER JOIN userstories_userstory ON userstories_userstory_watchers.userstory_id = userstories_userstory.id""".format(content_type_id=ContentType.objects.get(model='userstory').id)
cursor = connection.cursor()
cursor.execute(sql)
class Migration(migrations.Migration):

View File

@ -35,8 +35,16 @@ class UserStoryPermission(TaigaResourcePermission):
watch_perms = IsAuthenticated() & HasProjectPerm('view_us')
unwatch_perms = IsAuthenticated() & HasProjectPerm('view_us')
class UserStoryVotersPermission(TaigaResourcePermission):
enought_perms = IsProjectOwner() | IsSuperUser()
global_perms = None
retrieve_perms = HasProjectPerm('view_us')
list_perms = HasProjectPerm('view_us')
class UserStoryWatchersPermission(TaigaResourcePermission):
enought_perms = IsProjectOwner() | IsSuperUser()
global_perms = None
retrieve_perms = HasProjectPerm('view_us')
list_perms = HasProjectPerm('view_us')

View File

@ -31,6 +31,7 @@ from taiga.projects.userstories.apps import (
disconnect_userstories_signals)
from taiga.events import events
from taiga.projects.votes import services as votes_services
from . import models
@ -138,7 +139,8 @@ def userstories_to_csv(project,queryset):
"created_date", "modified_date", "finish_date",
"client_requirement", "team_requirement", "attachments",
"generated_from_issue", "external_reference", "tasks",
"tags"]
"tags",
"watchers", "voters"]
for custom_attr in project.userstorycustomattributes.all():
fieldnames.append(custom_attr.name)
@ -170,6 +172,8 @@ def userstories_to_csv(project,queryset):
"external_reference": us.external_reference,
"tasks": ",".join([str(task.ref) for task in us.tasks.all()]),
"tags": ",".join(us.tags or []),
"watchers": [u.id for u in us.get_watchers()],
"voters": votes_services.get_voters(us).count(),
}
for role in us.project.roles.filter(computable=True).order_by('name'):

View File

@ -21,13 +21,13 @@ from taiga.base.api.permissions import IsAuthenticated
from taiga.base import filters
from taiga.base import exceptions as exc
from taiga.base import response
from taiga.base.api import ModelCrudViewSet
from taiga.base.api import ModelCrudViewSet, ModelListViewSet
from taiga.base.api.utils import get_object_or_404
from taiga.base.decorators import list_route
from taiga.projects.models import Project
from taiga.mdrender.service import render as mdrender
from taiga.projects.notifications.mixins import WatchedResourceMixin
from taiga.projects.notifications.mixins import WatchedResourceMixin, WatchersViewSetMixin
from taiga.projects.history.mixins import HistoryResourceMixin
from taiga.projects.occ import OCCResourceMixin
@ -43,6 +43,12 @@ class WikiViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
permission_classes = (permissions.WikiPagePermission,)
filter_backends = (filters.CanViewWikiPagesFilterBackend,)
filter_fields = ("project", "slug")
queryset = models.WikiPage.objects.all()
def get_queryset(self):
qs = super().get_queryset()
qs = self.attach_watchers_attrs_to_queryset(qs)
return qs
@list_route(methods=["GET"])
def by_slug(self, request):
@ -77,6 +83,11 @@ class WikiViewSet(OCCResourceMixin, HistoryResourceMixin, WatchedResourceMixin,
super().pre_save(obj)
class WikiWatchersViewSet(WatchersViewSetMixin, ModelListViewSet):
permission_classes = (permissions.WikiPageWatchersPermission,)
resource_model = models.WikiPage
class WikiLinkViewSet(ModelCrudViewSet):
model = models.WikiLink
serializer_class = serializers.WikiLinkSerializer

View File

@ -1,17 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.apps import apps
from django.db import connection
from django.db import models, migrations
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.management import update_all_contenttypes
def create_notifications(apps, schema_editor):
update_all_contenttypes()
migrations.RunSQL(sql="""
INSERT INTO notifications_watched (object_id, created_date, content_type_id, user_id)
SELECT wikipage_id AS object_id, now() AS created_date, {content_type_id} AS content_type_id, user_id
FROM wiki_wikipage_watchers""".format(content_type_id=ContentType.objects.get(model='wikipage').id)),
update_all_contenttypes()
sql="""
INSERT INTO notifications_watched (object_id, created_date, content_type_id, user_id, project_id)
SELECT wikipage_id AS object_id, now() AS created_date, {content_type_id} AS content_type_id, user_id, project_id
FROM wiki_wikipage_watchers INNER JOIN wiki_wikipage ON wiki_wikipage_watchers.wikipage_id = wiki_wikipage.id""".format(content_type_id=ContentType.objects.get(model='wikipage').id)
cursor = connection.cursor()
cursor.execute(sql)
class Migration(migrations.Migration):

View File

@ -15,7 +15,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from taiga.base.api.permissions import (TaigaResourcePermission, HasProjectPerm,
IsProjectOwner, AllowAny, IsSuperUser)
IsAuthenticated, IsProjectOwner, AllowAny,
IsSuperUser)
class WikiPagePermission(TaigaResourcePermission):
@ -29,6 +30,16 @@ class WikiPagePermission(TaigaResourcePermission):
destroy_perms = HasProjectPerm('delete_wiki_page')
list_perms = AllowAny()
render_perms = AllowAny()
watch_perms = IsAuthenticated() & HasProjectPerm('view_wiki_pages')
unwatch_perms = IsAuthenticated() & HasProjectPerm('view_wiki_pages')
class WikiPageWatchersPermission(TaigaResourcePermission):
enought_perms = IsProjectOwner() | IsSuperUser()
global_perms = None
retrieve_perms = HasProjectPerm('view_wiki_pages')
list_perms = HasProjectPerm('view_wiki_pages')
class WikiLinkPermission(TaigaResourcePermission):
enought_perms = IsProjectOwner() | IsSuperUser()

View File

@ -22,10 +22,6 @@ from taiga.mdrender.service import render as mdrender
from . import models
from taiga.projects.history import services as history_service
from taiga.mdrender.service import render as mdrender
class WikiPageSerializer(WatchersValidator, WatchedResourceModelSerializer, serializers.ModelSerializer):
html = serializers.SerializerMethodField("get_html")

View File

@ -49,6 +49,7 @@ router.register(r"notify-policies", NotifyPolicyViewSet, base_name="notification
# Projects & Selectors
from taiga.projects.api import ProjectViewSet
from taiga.projects.api import ProjectFansViewSet
from taiga.projects.api import ProjectWatchersViewSet
from taiga.projects.api import MembershipViewSet
from taiga.projects.api import InvitationViewSet
from taiga.projects.api import UserStoryStatusViewSet
@ -62,6 +63,7 @@ from taiga.projects.api import ProjectTemplateViewSet
router.register(r"projects", ProjectViewSet, base_name="projects")
router.register(r"projects/(?P<resource_id>\d+)/fans", ProjectFansViewSet, base_name="project-fans")
router.register(r"projects/(?P<resource_id>\d+)/watchers", ProjectWatchersViewSet, base_name="project-watchers")
router.register(r"project-templates", ProjectTemplateViewSet, base_name="project-templates")
router.register(r"memberships", MembershipViewSet, base_name="memberships")
router.register(r"invitations", InvitationViewSet, base_name="invitations")
@ -124,22 +126,33 @@ router.register(r"wiki/attachments", WikiAttachmentViewSet, base_name="wiki-atta
# Project components
from taiga.projects.milestones.api import MilestoneViewSet
from taiga.projects.milestones.api import MilestoneWatchersViewSet
from taiga.projects.userstories.api import UserStoryViewSet
from taiga.projects.userstories.api import UserStoryVotersViewSet
from taiga.projects.userstories.api import UserStoryWatchersViewSet
from taiga.projects.tasks.api import TaskViewSet
from taiga.projects.tasks.api import TaskVotersViewSet
from taiga.projects.tasks.api import TaskWatchersViewSet
from taiga.projects.issues.api import IssueViewSet
from taiga.projects.issues.api import IssueVotersViewSet
from taiga.projects.wiki.api import WikiViewSet, WikiLinkViewSet
from taiga.projects.issues.api import IssueWatchersViewSet
from taiga.projects.wiki.api import WikiViewSet
from taiga.projects.wiki.api import WikiLinkViewSet
from taiga.projects.wiki.api import WikiWatchersViewSet
router.register(r"milestones", MilestoneViewSet, base_name="milestones")
router.register(r"milestones/(?P<resource_id>\d+)/watchers", MilestoneWatchersViewSet, base_name="milestone-watchers")
router.register(r"userstories", UserStoryViewSet, base_name="userstories")
router.register(r"userstories/(?P<resource_id>\d+)/voters", UserStoryVotersViewSet, base_name="userstory-voters")
router.register(r"userstories/(?P<resource_id>\d+)/watchers", UserStoryWatchersViewSet, base_name="userstory-watchers")
router.register(r"tasks", TaskViewSet, base_name="tasks")
router.register(r"tasks/(?P<resource_id>\d+)/voters", TaskVotersViewSet, base_name="task-voters")
router.register(r"tasks/(?P<resource_id>\d+)/watchers", TaskWatchersViewSet, base_name="task-watchers")
router.register(r"issues", IssueViewSet, base_name="issues")
router.register(r"issues/(?P<resource_id>\d+)/voters", IssueVotersViewSet, base_name="issue-voters")
router.register(r"issues/(?P<resource_id>\d+)/watchers", IssueWatchersViewSet, base_name="issue-watchers")
router.register(r"wiki", WikiViewSet, base_name="wiki")
router.register(r"wiki/(?P<resource_id>\d+)/watchers", WikiWatchersViewSet, base_name="wiki-watchers")
router.register(r"wiki-links", WikiLinkViewSet, base_name="wiki-links")

View File

@ -22,14 +22,12 @@ from taiga.projects.history import services as history_services
from taiga.projects.models import Project
from taiga.users.models import User
from taiga.projects.history.choices import HistoryType
from taiga.projects.notifications import services as notifications_services
from taiga.timeline.service import (push_to_timeline,
build_user_namespace,
build_project_namespace,
extract_user_info)
# TODO: Add events to followers timeline when followers are implemented.
# TODO: Add events to project watchers timeline when project watchers are implemented.
def _push_to_timeline(*args, **kwargs):
if settings.CELERY_ENABLED:
@ -60,9 +58,9 @@ def _push_to_timelines(project, user, obj, event_type, created_datetime, extra_d
related_people |= User.objects.filter(id=obj.assigned_to_id)
## - Watchers
watchers = getattr(obj, "watchers", None)
watchers = notifications_services.get_watchers(obj)
if watchers:
related_people |= obj.get_watchers()
related_people |= watchers
## - Exclude inactive and system users and remove duplicate
related_people = related_people.exclude(is_active=False)

View File

@ -441,6 +441,17 @@ class VotesFactory(Factory):
object_id = factory.Sequence(lambda n: n)
class WatchedFactory(Factory):
class Meta:
model = "notifications.Watched"
strategy = factory.CREATE_STRATEGY
content_type = factory.SubFactory("tests.factories.ContentTypeFactory")
object_id = factory.Sequence(lambda n: n)
user = factory.SubFactory("tests.factories.UserFactory")
project = factory.SubFactory("tests.factories.ProjectFactory")
class ContentTypeFactory(Factory):
class Meta:
model = "contenttypes.ContentType"

View File

@ -9,6 +9,7 @@ from taiga.base.utils import json
from tests import factories as f
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
from taiga.projects.votes.services import add_vote
from taiga.projects.notifications.services import add_watcher
from taiga.projects.occ import OCCResourceMixin
from unittest import mock
@ -616,3 +617,51 @@ def test_issue_action_unwatch(client, data):
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url2, "", users)
assert results == [404, 404, 404, 200, 200]
def test_issue_watchers_list(client, data):
public_url = reverse('issue-watchers-list', kwargs={"resource_id": data.public_issue.pk})
private_url1 = reverse('issue-watchers-list', kwargs={"resource_id": data.private_issue1.pk})
private_url2 = reverse('issue-watchers-list', kwargs={"resource_id": data.private_issue2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_issue_watchers_retrieve(client, data):
add_watcher(data.public_issue, data.project_owner)
public_url = reverse('issue-watchers-detail', kwargs={"resource_id": data.public_issue.pk,
"pk": data.project_owner.pk})
add_watcher(data.private_issue1, data.project_owner)
private_url1 = reverse('issue-watchers-detail', kwargs={"resource_id": data.private_issue1.pk,
"pk": data.project_owner.pk})
add_watcher(data.private_issue2, data.project_owner)
private_url2 = reverse('issue-watchers-detail', kwargs={"resource_id": data.private_issue2.pk,
"pk": data.project_owner.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]

View File

@ -3,6 +3,7 @@ from django.core.urlresolvers import reverse
from taiga.base.utils import json
from taiga.projects.milestones.serializers import MilestoneSerializer
from taiga.projects.milestones.models import Milestone
from taiga.projects.notifications.services import add_watcher
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
from tests import factories as f
@ -274,3 +275,93 @@ def test_milestone_action_stats(client, data):
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_milestone_action_watch(client, data):
public_url = reverse('milestones-watch', kwargs={"pk": data.public_milestone.pk})
private_url1 = reverse('milestones-watch', kwargs={"pk": data.private_milestone1.pk})
private_url2 = reverse('milestones-watch', kwargs={"pk": data.private_milestone2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'post', public_url, "", users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url1, "", users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url2, "", users)
assert results == [404, 404, 404, 200, 200]
def test_milestone_action_unwatch(client, data):
public_url = reverse('milestones-unwatch', kwargs={"pk": data.public_milestone.pk})
private_url1 = reverse('milestones-unwatch', kwargs={"pk": data.private_milestone1.pk})
private_url2 = reverse('milestones-unwatch', kwargs={"pk": data.private_milestone2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'post', public_url, "", users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url1, "", users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url2, "", users)
assert results == [404, 404, 404, 200, 200]
def test_milestone_watchers_list(client, data):
public_url = reverse('milestone-watchers-list', kwargs={"resource_id": data.public_milestone.pk})
private_url1 = reverse('milestone-watchers-list', kwargs={"resource_id": data.private_milestone1.pk})
private_url2 = reverse('milestone-watchers-list', kwargs={"resource_id": data.private_milestone2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_milestone_watchers_retrieve(client, data):
add_watcher(data.public_milestone, data.project_owner)
public_url = reverse('milestone-watchers-detail', kwargs={"resource_id": data.public_milestone.pk,
"pk": data.project_owner.pk})
add_watcher(data.private_milestone1, data.project_owner)
private_url1 = reverse('milestone-watchers-detail', kwargs={"resource_id": data.private_milestone1.pk,
"pk": data.project_owner.pk})
add_watcher(data.private_milestone2, data.project_owner)
private_url2 = reverse('milestone-watchers-detail', kwargs={"resource_id": data.private_milestone2.pk,
"pk": data.project_owner.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]

View File

@ -81,6 +81,13 @@ 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_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
@ -109,6 +116,7 @@ def test_project_update(client, data):
project_data = ProjectDetailSerializer(data.private_project2).data
project_data["is_private"] = False
project_data = json.dumps(project_data)
users = [
@ -300,6 +308,51 @@ def test_project_fans_retrieve(client, data):
assert results == [401, 403, 403, 200, 200]
def test_project_watchers_list(client, data):
public_url = reverse('project-watchers-list', kwargs={"resource_id": data.public_project.pk})
private1_url = reverse('project-watchers-list', kwargs={"resource_id": data.private_project1.pk})
private2_url = reverse('project-watchers-list', kwargs={"resource_id": data.private_project2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
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)]
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)]
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)]
def test_project_watchers_retrieve(client, data):
public_url = reverse('project-watchers-detail', kwargs={"resource_id": data.public_project.pk,
"pk": data.project_owner.pk})
private1_url = reverse('project-watchers-detail', kwargs={"resource_id": data.private_project1.pk,
"pk": data.project_owner.pk})
private2_url = reverse('project-watchers-detail', kwargs={"resource_id": data.private_project2.pk,
"pk": data.project_owner.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private1_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private2_url, None, users)
assert results == [401, 403, 403, 200, 200]
def test_project_action_create_template(client, data):
public_url = reverse('projects-create-template', kwargs={"pk": data.public_project.pk})
private1_url = reverse('projects-create-template', kwargs={"pk": data.private_project1.pk})

View File

@ -10,6 +10,7 @@ from taiga.projects.occ import OCCResourceMixin
from tests import factories as f
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
from taiga.projects.votes.services import add_vote
from taiga.projects.notifications.services import add_watcher
from unittest import mock
@ -571,3 +572,51 @@ def test_task_action_unwatch(client, data):
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url2, "", users)
assert results == [404, 404, 404, 200, 200]
def test_task_watchers_list(client, data):
public_url = reverse('task-watchers-list', kwargs={"resource_id": data.public_task.pk})
private_url1 = reverse('task-watchers-list', kwargs={"resource_id": data.private_task1.pk})
private_url2 = reverse('task-watchers-list', kwargs={"resource_id": data.private_task2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_task_watchers_retrieve(client, data):
add_watcher(data.public_task, data.project_owner)
public_url = reverse('task-watchers-detail', kwargs={"resource_id": data.public_task.pk,
"pk": data.project_owner.pk})
add_watcher(data.private_task1, data.project_owner)
private_url1 = reverse('task-watchers-detail', kwargs={"resource_id": data.private_task1.pk,
"pk": data.project_owner.pk})
add_watcher(data.private_task2, data.project_owner)
private_url2 = reverse('task-watchers-detail', kwargs={"resource_id": data.private_task2.pk,
"pk": data.project_owner.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]

View File

@ -10,6 +10,7 @@ from taiga.projects.occ import OCCResourceMixin
from tests import factories as f
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
from taiga.projects.votes.services import add_vote
from taiga.projects.notifications.services import add_watcher
from unittest import mock
@ -570,3 +571,51 @@ def test_user_story_action_unwatch(client, data):
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url2, "", users)
assert results == [404, 404, 404, 200, 200]
def test_userstory_watchers_list(client, data):
public_url = reverse('userstory-watchers-list', kwargs={"resource_id": data.public_user_story.pk})
private_url1 = reverse('userstory-watchers-list', kwargs={"resource_id": data.private_user_story1.pk})
private_url2 = reverse('userstory-watchers-list', kwargs={"resource_id": data.private_user_story2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_userstory_watchers_retrieve(client, data):
add_watcher(data.public_user_story, data.project_owner)
public_url = reverse('userstory-watchers-detail', kwargs={"resource_id": data.public_user_story.pk,
"pk": data.project_owner.pk})
add_watcher(data.private_user_story1, data.project_owner)
private_url1 = reverse('userstory-watchers-detail', kwargs={"resource_id": data.private_user_story1.pk,
"pk": data.project_owner.pk})
add_watcher(data.private_user_story2, data.project_owner)
private_url2 = reverse('userstory-watchers-detail', kwargs={"resource_id": data.private_user_story2.pk,
"pk": data.project_owner.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]

View File

@ -1,10 +1,11 @@
from django.core.urlresolvers import reverse
from taiga.base.utils import json
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
from taiga.projects.notifications.services import add_watcher
from taiga.projects.occ import OCCResourceMixin
from taiga.projects.wiki.serializers import WikiPageSerializer, WikiLinkSerializer
from taiga.projects.wiki.models import WikiPage, WikiLink
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
from taiga.projects.occ import OCCResourceMixin
from tests import factories as f
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
@ -436,3 +437,93 @@ def test_wiki_link_patch(client, data):
patch_data = json.dumps({"title": "test"})
results = helper_test_http_method(client, 'patch', private_url2, patch_data, users)
assert results == [401, 403, 403, 200, 200]
def test_wikipage_action_watch(client, data):
public_url = reverse('wiki-watch', kwargs={"pk": data.public_wiki_page.pk})
private_url1 = reverse('wiki-watch', kwargs={"pk": data.private_wiki_page1.pk})
private_url2 = reverse('wiki-watch', kwargs={"pk": data.private_wiki_page2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'post', public_url, "", users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url1, "", users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url2, "", users)
assert results == [404, 404, 404, 200, 200]
def test_wikipage_action_unwatch(client, data):
public_url = reverse('wiki-unwatch', kwargs={"pk": data.public_wiki_page.pk})
private_url1 = reverse('wiki-unwatch', kwargs={"pk": data.private_wiki_page1.pk})
private_url2 = reverse('wiki-unwatch', kwargs={"pk": data.private_wiki_page2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'post', public_url, "", users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url1, "", users)
assert results == [401, 200, 200, 200, 200]
results = helper_test_http_method(client, 'post', private_url2, "", users)
assert results == [404, 404, 404, 200, 200]
def test_wikipage_watchers_list(client, data):
public_url = reverse('wiki-watchers-list', kwargs={"resource_id": data.public_wiki_page.pk})
private_url1 = reverse('wiki-watchers-list', kwargs={"resource_id": data.private_wiki_page1.pk})
private_url2 = reverse('wiki-watchers-list', kwargs={"resource_id": data.private_wiki_page2.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]
def test_wikipage_watchers_retrieve(client, data):
add_watcher(data.public_wiki_page, data.project_owner)
public_url = reverse('wiki-watchers-detail', kwargs={"resource_id": data.public_wiki_page.pk,
"pk": data.project_owner.pk})
add_watcher(data.private_wiki_page1, data.project_owner)
private_url1 = reverse('wiki-watchers-detail', kwargs={"resource_id": data.private_wiki_page1.pk,
"pk": data.project_owner.pk})
add_watcher(data.private_wiki_page2, data.project_owner)
private_url2 = reverse('wiki-watchers-detail', kwargs={"resource_id": data.private_wiki_page2.pk,
"pk": data.project_owner.pk})
users = [
None,
data.registered_user,
data.project_member_without_perms,
data.project_member_with_perms,
data.project_owner
]
results = helper_test_http_method(client, 'get', public_url, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url1, None, users)
assert results == [200, 200, 200, 200, 200]
results = helper_test_http_method(client, 'get', private_url2, None, users)
assert results == [401, 403, 403, 200, 200]

View File

@ -412,6 +412,6 @@ def test_custom_fields_csv_generation():
data.seek(0)
reader = csv.reader(data)
row = next(reader)
assert row[16] == attr.name
assert row[18] == attr.name
row = next(reader)
assert row[16] == "val1"
assert row[18] == "val1"

View File

@ -211,6 +211,124 @@ def test_users_to_notify():
assert users == {member1.user, issue.get_owner()}
def test_watching_users_to_notify_on_issue_modification_1():
# If:
# - the user is watching the issue
# - the user is not watching the project
# - the notify policy is watch
# Then:
# - email is sent
project = f.ProjectFactory.create(anon_permissions= ["view_issues"])
issue = f.IssueFactory.create(project=project)
watching_user = f.UserFactory()
issue.add_watcher(watching_user)
watching_user_policy = services.get_notify_policy(project, watching_user)
issue.description = "test1"
issue.save()
watching_user_policy.notify_level = NotifyLevel.watch
users = services.get_users_to_notify(issue)
assert users == {watching_user, issue.owner}
def test_watching_users_to_notify_on_issue_modification_2():
# If:
# - the user is watching the issue
# - the user is not watching the project
# - the notify policy is notwatch
# Then:
# - email is sent
project = f.ProjectFactory.create(anon_permissions= ["view_issues"])
issue = f.IssueFactory.create(project=project)
watching_user = f.UserFactory()
issue.add_watcher(watching_user)
watching_user_policy = services.get_notify_policy(project, watching_user)
issue.description = "test1"
issue.save()
watching_user_policy.notify_level = NotifyLevel.notwatch
users = services.get_users_to_notify(issue)
assert users == {watching_user, issue.owner}
def test_watching_users_to_notify_on_issue_modification_3():
# If:
# - the user is watching the issue
# - the user is not watching the project
# - the notify policy is ignore
# Then:
# - email is not sent
project = f.ProjectFactory.create(anon_permissions= ["view_issues"])
issue = f.IssueFactory.create(project=project)
watching_user = f.UserFactory()
issue.add_watcher(watching_user)
watching_user_policy = services.get_notify_policy(project, watching_user)
issue.description = "test1"
issue.save()
watching_user_policy.notify_level = NotifyLevel.ignore
watching_user_policy.save()
users = services.get_users_to_notify(issue)
assert users == {issue.owner}
def test_watching_users_to_notify_on_issue_modification_4():
# If:
# - the user is not watching the issue
# - the user is watching the project
# - the notify policy is ignore
# Then:
# - email is not sent
project = f.ProjectFactory.create(anon_permissions= ["view_issues"])
issue = f.IssueFactory.create(project=project)
watching_user = f.UserFactory()
project.add_watcher(watching_user)
watching_user_policy = services.get_notify_policy(project, watching_user)
issue.description = "test1"
issue.save()
watching_user_policy.notify_level = NotifyLevel.ignore
watching_user_policy.save()
users = services.get_users_to_notify(issue)
assert users == {issue.owner}
def test_watching_users_to_notify_on_issue_modification_5():
# If:
# - the user is not watching the issue
# - the user is watching the project
# - the notify policy is watch
# Then:
# - email is sent
project = f.ProjectFactory.create(anon_permissions= ["view_issues"])
issue = f.IssueFactory.create(project=project)
watching_user = f.UserFactory()
project.add_watcher(watching_user)
watching_user_policy = services.get_notify_policy(project, watching_user)
issue.description = "test1"
issue.save()
watching_user_policy.notify_level = NotifyLevel.watch
watching_user_policy.save()
users = services.get_users_to_notify(issue)
assert users == {watching_user, issue.owner}
def test_watching_users_to_notify_on_issue_modification_6():
# If:
# - the user is not watching the issue
# - the user is watching the project
# - the notify policy is notwatch
# Then:
# - email is sent
project = f.ProjectFactory.create(anon_permissions= ["view_issues"])
issue = f.IssueFactory.create(project=project)
watching_user = f.UserFactory()
project.add_watcher(watching_user)
watching_user_policy = services.get_notify_policy(project, watching_user)
issue.description = "test1"
issue.save()
watching_user_policy.notify_level = NotifyLevel.notwatch
watching_user_policy.save()
users = services.get_users_to_notify(issue)
assert users == {watching_user, issue.owner}
def test_send_notifications_using_services_method(settings, mail):
settings.CHANGE_NOTIFICATIONS_MIN_INTERVAL = 1

View File

@ -163,6 +163,6 @@ def test_custom_fields_csv_generation():
data.seek(0)
reader = csv.reader(data)
row = next(reader)
assert row[17] == attr.name
assert row[19] == attr.name
row = next(reader)
assert row[17] == "val1"
assert row[19] == "val1"

View File

@ -196,8 +196,10 @@ def test_create_membership_timeline():
def test_update_project_timeline():
user_watcher= factories.UserFactory()
project = factories.ProjectFactory.create(name="test project timeline")
history_services.take_snapshot(project, user=project.owner)
project.add_watcher(user_watcher)
project.name = "test project timeline updated"
project.save()
history_services.take_snapshot(project, user=project.owner)
@ -206,11 +208,18 @@ def test_update_project_timeline():
assert project_timeline[0].data["project"]["name"] == "test project timeline updated"
assert project_timeline[0].data["values_diff"]["name"][0] == "test project timeline"
assert project_timeline[0].data["values_diff"]["name"][1] == "test project timeline updated"
user_watcher_timeline = service.get_profile_timeline(user_watcher)
assert user_watcher_timeline[0].event_type == "projects.project.change"
assert user_watcher_timeline[0].data["project"]["name"] == "test project timeline updated"
assert user_watcher_timeline[0].data["values_diff"]["name"][0] == "test project timeline"
assert user_watcher_timeline[0].data["values_diff"]["name"][1] == "test project timeline updated"
def test_update_milestone_timeline():
user_watcher= factories.UserFactory()
milestone = factories.MilestoneFactory.create(name="test milestone timeline")
history_services.take_snapshot(milestone, user=milestone.owner)
milestone.add_watcher(user_watcher)
milestone.name = "test milestone timeline updated"
milestone.save()
history_services.take_snapshot(milestone, user=milestone.owner)
@ -219,11 +228,18 @@ def test_update_milestone_timeline():
assert project_timeline[0].data["milestone"]["name"] == "test milestone timeline updated"
assert project_timeline[0].data["values_diff"]["name"][0] == "test milestone timeline"
assert project_timeline[0].data["values_diff"]["name"][1] == "test milestone timeline updated"
user_watcher_timeline = service.get_profile_timeline(user_watcher)
assert user_watcher_timeline[0].event_type == "milestones.milestone.change"
assert user_watcher_timeline[0].data["milestone"]["name"] == "test milestone timeline updated"
assert user_watcher_timeline[0].data["values_diff"]["name"][0] == "test milestone timeline"
assert user_watcher_timeline[0].data["values_diff"]["name"][1] == "test milestone timeline updated"
def test_update_user_story_timeline():
user_watcher= factories.UserFactory()
user_story = factories.UserStoryFactory.create(subject="test us timeline")
history_services.take_snapshot(user_story, user=user_story.owner)
user_story.add_watcher(user_watcher)
user_story.subject = "test us timeline updated"
user_story.save()
history_services.take_snapshot(user_story, user=user_story.owner)
@ -232,11 +248,18 @@ def test_update_user_story_timeline():
assert project_timeline[0].data["userstory"]["subject"] == "test us timeline updated"
assert project_timeline[0].data["values_diff"]["subject"][0] == "test us timeline"
assert project_timeline[0].data["values_diff"]["subject"][1] == "test us timeline updated"
user_watcher_timeline = service.get_profile_timeline(user_watcher)
assert user_watcher_timeline[0].event_type == "userstories.userstory.change"
assert user_watcher_timeline[0].data["userstory"]["subject"] == "test us timeline updated"
assert user_watcher_timeline[0].data["values_diff"]["subject"][0] == "test us timeline"
assert user_watcher_timeline[0].data["values_diff"]["subject"][1] == "test us timeline updated"
def test_update_issue_timeline():
user_watcher= factories.UserFactory()
issue = factories.IssueFactory.create(subject="test issue timeline")
history_services.take_snapshot(issue, user=issue.owner)
issue.add_watcher(user_watcher)
issue.subject = "test issue timeline updated"
issue.save()
history_services.take_snapshot(issue, user=issue.owner)
@ -245,11 +268,18 @@ def test_update_issue_timeline():
assert project_timeline[0].data["issue"]["subject"] == "test issue timeline updated"
assert project_timeline[0].data["values_diff"]["subject"][0] == "test issue timeline"
assert project_timeline[0].data["values_diff"]["subject"][1] == "test issue timeline updated"
user_watcher_timeline = service.get_profile_timeline(user_watcher)
assert user_watcher_timeline[0].event_type == "issues.issue.change"
assert user_watcher_timeline[0].data["issue"]["subject"] == "test issue timeline updated"
assert user_watcher_timeline[0].data["values_diff"]["subject"][0] == "test issue timeline"
assert user_watcher_timeline[0].data["values_diff"]["subject"][1] == "test issue timeline updated"
def test_update_task_timeline():
user_watcher= factories.UserFactory()
task = factories.TaskFactory.create(subject="test task timeline")
history_services.take_snapshot(task, user=task.owner)
task.add_watcher(user_watcher)
task.subject = "test task timeline updated"
task.save()
history_services.take_snapshot(task, user=task.owner)
@ -258,11 +288,18 @@ def test_update_task_timeline():
assert project_timeline[0].data["task"]["subject"] == "test task timeline updated"
assert project_timeline[0].data["values_diff"]["subject"][0] == "test task timeline"
assert project_timeline[0].data["values_diff"]["subject"][1] == "test task timeline updated"
user_watcher_timeline = service.get_profile_timeline(user_watcher)
assert user_watcher_timeline[0].event_type == "tasks.task.change"
assert user_watcher_timeline[0].data["task"]["subject"] == "test task timeline updated"
assert user_watcher_timeline[0].data["values_diff"]["subject"][0] == "test task timeline"
assert user_watcher_timeline[0].data["values_diff"]["subject"][1] == "test task timeline updated"
def test_update_wiki_page_timeline():
user_watcher= factories.UserFactory()
page = factories.WikiPageFactory.create(slug="test wiki page timeline")
history_services.take_snapshot(page, user=page.owner)
page.add_watcher(user_watcher)
page.slug = "test wiki page timeline updated"
page.save()
history_services.take_snapshot(page, user=page.owner)
@ -271,6 +308,11 @@ def test_update_wiki_page_timeline():
assert project_timeline[0].data["wikipage"]["slug"] == "test wiki page timeline updated"
assert project_timeline[0].data["values_diff"]["slug"][0] == "test wiki page timeline"
assert project_timeline[0].data["values_diff"]["slug"][1] == "test wiki page timeline updated"
user_watcher_timeline = service.get_profile_timeline(user_watcher)
assert user_watcher_timeline[0].event_type == "wiki.wikipage.change"
assert user_watcher_timeline[0].data["wikipage"]["slug"] == "test wiki page timeline updated"
assert user_watcher_timeline[0].data["values_diff"]["slug"][0] == "test wiki page timeline"
assert user_watcher_timeline[0].data["values_diff"]["slug"][1] == "test wiki page timeline updated"
def test_update_membership_timeline():
@ -298,50 +340,80 @@ def test_update_membership_timeline():
def test_delete_project_timeline():
project = factories.ProjectFactory.create(name="test project timeline")
user_watcher= factories.UserFactory()
project.add_watcher(user_watcher)
history_services.take_snapshot(project, user=project.owner, delete=True)
user_timeline = service.get_project_timeline(project)
assert user_timeline[0].event_type == "projects.project.delete"
assert user_timeline[0].data["project"]["id"] == project.id
user_watcher_timeline = service.get_profile_timeline(user_watcher)
assert user_watcher_timeline[0].event_type == "projects.project.delete"
assert user_watcher_timeline[0].data["project"]["id"] == project.id
def test_delete_milestone_timeline():
milestone = factories.MilestoneFactory.create(name="test milestone timeline")
user_watcher= factories.UserFactory()
milestone.add_watcher(user_watcher)
history_services.take_snapshot(milestone, user=milestone.owner, delete=True)
project_timeline = service.get_project_timeline(milestone.project)
assert project_timeline[0].event_type == "milestones.milestone.delete"
assert project_timeline[0].data["milestone"]["name"] == "test milestone timeline"
user_watcher_timeline = service.get_profile_timeline(user_watcher)
assert user_watcher_timeline[0].event_type == "milestones.milestone.delete"
assert user_watcher_timeline[0].data["milestone"]["name"] == "test milestone timeline"
def test_delete_user_story_timeline():
user_story = factories.UserStoryFactory.create(subject="test us timeline")
user_watcher= factories.UserFactory()
user_story.add_watcher(user_watcher)
history_services.take_snapshot(user_story, user=user_story.owner, delete=True)
project_timeline = service.get_project_timeline(user_story.project)
assert project_timeline[0].event_type == "userstories.userstory.delete"
assert project_timeline[0].data["userstory"]["subject"] == "test us timeline"
user_watcher_timeline = service.get_profile_timeline(user_watcher)
assert user_watcher_timeline[0].event_type == "userstories.userstory.delete"
assert user_watcher_timeline[0].data["userstory"]["subject"] == "test us timeline"
def test_delete_issue_timeline():
issue = factories.IssueFactory.create(subject="test issue timeline")
user_watcher= factories.UserFactory()
issue.add_watcher(user_watcher)
history_services.take_snapshot(issue, user=issue.owner, delete=True)
project_timeline = service.get_project_timeline(issue.project)
assert project_timeline[0].event_type == "issues.issue.delete"
assert project_timeline[0].data["issue"]["subject"] == "test issue timeline"
user_watcher_timeline = service.get_profile_timeline(user_watcher)
assert user_watcher_timeline[0].event_type == "issues.issue.delete"
assert user_watcher_timeline[0].data["issue"]["subject"] == "test issue timeline"
def test_delete_task_timeline():
task = factories.TaskFactory.create(subject="test task timeline")
user_watcher= factories.UserFactory()
task.add_watcher(user_watcher)
history_services.take_snapshot(task, user=task.owner, delete=True)
project_timeline = service.get_project_timeline(task.project)
assert project_timeline[0].event_type == "tasks.task.delete"
assert project_timeline[0].data["task"]["subject"] == "test task timeline"
user_watcher_timeline = service.get_profile_timeline(user_watcher)
assert user_watcher_timeline[0].event_type == "tasks.task.delete"
assert user_watcher_timeline[0].data["task"]["subject"] == "test task timeline"
def test_delete_wiki_page_timeline():
page = factories.WikiPageFactory.create(slug="test wiki page timeline")
user_watcher= factories.UserFactory()
page.add_watcher(user_watcher)
history_services.take_snapshot(page, user=page.owner, delete=True)
project_timeline = service.get_project_timeline(page.project)
assert project_timeline[0].event_type == "wiki.wikipage.delete"
assert project_timeline[0].data["wikipage"]["slug"] == "test wiki page timeline"
user_watcher_timeline = service.get_profile_timeline(user_watcher)
assert user_watcher_timeline[0].event_type == "wiki.wikipage.delete"
assert user_watcher_timeline[0].data["wikipage"]["slug"] == "test wiki page timeline"
def test_delete_membership_timeline():

View File

@ -483,6 +483,6 @@ def test_custom_fields_csv_generation():
data.seek(0)
reader = csv.reader(data)
row = next(reader)
assert row[24] == attr.name
assert row[26] == attr.name
row = next(reader)
assert row[24] == "val1"
assert row[26] == "val1"

View File

@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
import json
from django.core.urlresolvers import reverse
from .. import factories as f
@ -45,3 +46,78 @@ def test_unwatch_issue(client):
response = client.post(url)
assert response.status_code == 200
def test_list_issue_watchers(client):
user = f.UserFactory.create()
issue = f.IssueFactory(owner=user)
f.MembershipFactory.create(project=issue.project, user=user, is_owner=True)
f.WatchedFactory.create(content_object=issue, user=user)
url = reverse("issue-watchers-list", args=(issue.id,))
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data[0]['id'] == user.id
def test_get_issue_watcher(client):
user = f.UserFactory.create()
issue = f.IssueFactory(owner=user)
f.MembershipFactory.create(project=issue.project, user=user, is_owner=True)
watch = f.WatchedFactory.create(content_object=issue, user=user)
url = reverse("issue-watchers-detail", args=(issue.id, watch.user.id))
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data['id'] == watch.user.id
def test_get_issue_watchers(client):
user = f.UserFactory.create()
issue = f.IssueFactory(owner=user)
f.MembershipFactory.create(project=issue.project, user=user, is_owner=True)
url = reverse("issues-detail", args=(issue.id,))
f.WatchedFactory.create(content_object=issue, user=user)
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data['watchers'] == [user.id]
def test_get_issue_is_watched(client):
user = f.UserFactory.create()
issue = f.IssueFactory(owner=user)
f.MembershipFactory.create(project=issue.project, user=user, is_owner=True)
url_detail = reverse("issues-detail", args=(issue.id,))
url_watch = reverse("issues-watch", args=(issue.id,))
url_unwatch = reverse("issues-unwatch", args=(issue.id,))
client.login(user)
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['watchers'] == []
assert response.data['is_watched'] == False
response = client.post(url_watch)
assert response.status_code == 200
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['watchers'] == [user.id]
assert response.data['is_watched'] == True
response = client.post(url_unwatch)
assert response.status_code == 200
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['watchers'] == []
assert response.data['is_watched'] == False

View File

@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
import json
from django.core.urlresolvers import reverse
from .. import factories as f
@ -45,3 +46,78 @@ def test_unwacth_project(client):
response = client.post(url)
assert response.status_code == 200
def test_list_project_watchers(client):
user = f.UserFactory.create()
project = f.create_project(owner=user)
f.MembershipFactory.create(project=project, user=user, is_owner=True)
f.WatchedFactory.create(content_object=project, user=user)
url = reverse("project-watchers-list", args=(project.id,))
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data[0]['id'] == user.id
def test_get_project_watcher(client):
user = f.UserFactory.create()
project = f.create_project(owner=user)
f.MembershipFactory.create(project=project, user=user, is_owner=True)
watch = f.WatchedFactory.create(content_object=project, user=user)
url = reverse("project-watchers-detail", args=(project.id, watch.user.id))
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data['id'] == watch.user.id
def test_get_project_watchers(client):
user = f.UserFactory.create()
project = f.create_project(owner=user)
f.MembershipFactory.create(project=project, user=user, is_owner=True)
url = reverse("projects-detail", args=(project.id,))
f.WatchedFactory.create(content_object=project, user=user)
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data['watchers'] == [user.id]
def test_get_project_is_watched(client):
user = f.UserFactory.create()
project = f.create_project(owner=user)
f.MembershipFactory.create(project=project, user=user, is_owner=True)
url_detail = reverse("projects-detail", args=(project.id,))
url_watch = reverse("projects-watch", args=(project.id,))
url_unwatch = reverse("projects-unwatch", args=(project.id,))
client.login(user)
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['watchers'] == []
assert response.data['is_watched'] == False
response = client.post(url_watch)
assert response.status_code == 200
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['watchers'] == [user.id]
assert response.data['is_watched'] == True
response = client.post(url_unwatch)
assert response.status_code == 200
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['watchers'] == []
assert response.data['is_watched'] == False

View File

@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
import json
from django.core.urlresolvers import reverse
from .. import factories as f
@ -45,3 +46,78 @@ def test_unwatch_task(client):
response = client.post(url)
assert response.status_code == 200
def test_list_task_watchers(client):
user = f.UserFactory.create()
task = f.TaskFactory(owner=user)
f.MembershipFactory.create(project=task.project, user=user, is_owner=True)
f.WatchedFactory.create(content_object=task, user=user)
url = reverse("task-watchers-list", args=(task.id,))
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data[0]['id'] == user.id
def test_get_task_watcher(client):
user = f.UserFactory.create()
task = f.TaskFactory(owner=user)
f.MembershipFactory.create(project=task.project, user=user, is_owner=True)
watch = f.WatchedFactory.create(content_object=task, user=user)
url = reverse("task-watchers-detail", args=(task.id, watch.user.id))
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data['id'] == watch.user.id
def test_get_task_watchers(client):
user = f.UserFactory.create()
task = f.TaskFactory(owner=user)
f.MembershipFactory.create(project=task.project, user=user, is_owner=True)
url = reverse("tasks-detail", args=(task.id,))
f.WatchedFactory.create(content_object=task, user=user)
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data['watchers'] == [user.id]
def test_get_task_is_watched(client):
user = f.UserFactory.create()
task = f.TaskFactory(owner=user)
f.MembershipFactory.create(project=task.project, user=user, is_owner=True)
url_detail = reverse("tasks-detail", args=(task.id,))
url_watch = reverse("tasks-watch", args=(task.id,))
url_unwatch = reverse("tasks-unwatch", args=(task.id,))
client.login(user)
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['watchers'] == []
assert response.data['is_watched'] == False
response = client.post(url_watch)
assert response.status_code == 200
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['watchers'] == [user.id]
assert response.data['is_watched'] == True
response = client.post(url_unwatch)
assert response.status_code == 200
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['watchers'] == []
assert response.data['is_watched'] == False

View File

@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
import json
from django.core.urlresolvers import reverse
from .. import factories as f
@ -45,3 +46,78 @@ def test_unwatch_user_story(client):
response = client.post(url)
assert response.status_code == 200
def test_list_user_story_watchers(client):
user = f.UserFactory.create()
user_story = f.UserStoryFactory(owner=user)
f.MembershipFactory.create(project=user_story.project, user=user, is_owner=True)
f.WatchedFactory.create(content_object=user_story, user=user)
url = reverse("userstory-watchers-list", args=(user_story.id,))
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data[0]['id'] == user.id
def test_get_user_story_watcher(client):
user = f.UserFactory.create()
user_story = f.UserStoryFactory(owner=user)
f.MembershipFactory.create(project=user_story.project, user=user, is_owner=True)
watch = f.WatchedFactory.create(content_object=user_story, user=user)
url = reverse("userstory-watchers-detail", args=(user_story.id, watch.user.id))
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data['id'] == watch.user.id
def test_get_user_story_watchers(client):
user = f.UserFactory.create()
user_story = f.UserStoryFactory(owner=user)
f.MembershipFactory.create(project=user_story.project, user=user, is_owner=True)
url = reverse("userstories-detail", args=(user_story.id,))
f.WatchedFactory.create(content_object=user_story, user=user)
client.login(user)
response = client.get(url)
assert response.status_code == 200
assert response.data['watchers'] == [user.id]
def test_get_user_story_is_watched(client):
user = f.UserFactory.create()
user_story = f.UserStoryFactory(owner=user)
f.MembershipFactory.create(project=user_story.project, user=user, is_owner=True)
url_detail = reverse("userstories-detail", args=(user_story.id,))
url_watch = reverse("userstories-watch", args=(user_story.id,))
url_unwatch = reverse("userstories-unwatch", args=(user_story.id,))
client.login(user)
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['watchers'] == []
assert response.data['is_watched'] == False
response = client.post(url_watch)
assert response.status_code == 200
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['watchers'] == [user.id]
assert response.data['is_watched'] == True
response = client.post(url_unwatch)
assert response.status_code == 200
response = client.get(url_detail)
assert response.status_code == 200
assert response.data['watchers'] == []
assert response.data['is_watched'] == False

View File

@ -90,3 +90,26 @@ def test_new_object_with_two_webhook(settings):
with patch('taiga.webhooks.tasks.delete_webhook') as delete_webhook_mock:
services.take_snapshot(obj, user=obj.owner, comment="test", delete=True)
assert delete_webhook_mock.call_count == 2
def test_send_request_one_webhook(settings):
settings.WEBHOOKS_ENABLED = True
project = f.ProjectFactory()
f.WebhookFactory.create(project=project)
objects = [
f.IssueFactory.create(project=project),
f.TaskFactory.create(project=project),
f.UserStoryFactory.create(project=project),
f.WikiPageFactory.create(project=project)
]
for obj in objects:
with patch('taiga.webhooks.tasks._send_request') as _send_request_mock:
services.take_snapshot(obj, user=obj.owner, comment="test")
assert _send_request_mock.call_count == 1
for obj in objects:
with patch('taiga.webhooks.tasks._send_request') as _send_request_mock:
services.take_snapshot(obj, user=obj.owner, comment="test", delete=True)
assert _send_request_mock.call_count == 1