Notifications & History: modify all related models and resources for work with new api.

remotes/origin/enhancement/email-actions
Andrey Antukh 2014-06-05 12:49:01 +02:00
parent 9ee7335ff0
commit 3083b7f232
16 changed files with 293 additions and 137 deletions

View File

@ -22,15 +22,18 @@ from rest_framework.permissions import IsAuthenticated
from taiga.base.api import ModelCrudViewSet from taiga.base.api import ModelCrudViewSet
from taiga.base import filters from taiga.base import filters
from taiga.base import exceptions as exc from taiga.base import exceptions as exc
from taiga.projects.mixins.notifications import NotificationSenderMixin
from taiga.projects.history.services import take_snapshot from taiga.projects.history.services import take_snapshot
from taiga.projects.notifications import WatchedResourceMixin
from taiga.projects.history import HistoryResourceMixin
from . import permissions from . import permissions
from . import serializers from . import serializers
from . import models from . import models
class BaseAttachmentViewSet(NotificationSenderMixin, ModelCrudViewSet): class BaseAttachmentViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet):
model = models.Attachment model = models.Attachment
serializer_class = serializers.AttachmentSerializer serializer_class = serializers.AttachmentSerializer
permission_classes = (IsAuthenticated, permissions.AttachmentPermission,) permission_classes = (IsAuthenticated, permissions.AttachmentPermission,)
@ -65,22 +68,9 @@ class BaseAttachmentViewSet(NotificationSenderMixin, ModelCrudViewSet):
raise exc.PermissionDenied(_("You don't have permissions for " raise exc.PermissionDenied(_("You don't have permissions for "
"add attachments to this user story")) "add attachments to this user story"))
def _get_object_for_snapshot(self, obj): def get_object_for_snapshot(self, obj):
return obj.content_object return obj.content_object
def pre_destroy(self, obj):
pass
def post_destroy(self, obj):
user = self.request.user
comment = self.request.DATA.get("comment", "")
obj = self._get_object_for_snapshot(obj)
history = take_snapshot(obj, comment=comment, user=user)
if history:
self._post_save_notification_sender(obj, history)
class UserStoryAttachmentViewSet(BaseAttachmentViewSet): class UserStoryAttachmentViewSet(BaseAttachmentViewSet):
content_type = "userstories.userstory" content_type = "userstories.userstory"

View File

@ -27,7 +27,9 @@ from taiga.base import exceptions as exc
from taiga.base.decorators import list_route, detail_route from taiga.base.decorators import list_route, detail_route
from taiga.base.api import ModelCrudViewSet from taiga.base.api import ModelCrudViewSet
from taiga.projects.mixins.notifications import NotificationSenderMixin from taiga.projects.notifications import WatchedResourceMixin
from taiga.projects.history import HistoryResourceMixin
from taiga.projects.votes.utils import attach_votescount_to_queryset from taiga.projects.votes.utils import attach_votescount_to_queryset
from taiga.projects.votes import services as votes_service from taiga.projects.votes import services as votes_service
@ -87,6 +89,7 @@ class IssuesFilter(filters.FilterBackend):
return queryset return queryset
class IssuesOrdering(filters.FilterBackend): class IssuesOrdering(filters.FilterBackend):
def filter_queryset(self, request, queryset, view): def filter_queryset(self, request, queryset, view):
order_by = request.QUERY_PARAMS.get('order_by', None) order_by = request.QUERY_PARAMS.get('order_by', None)
@ -99,25 +102,27 @@ class IssuesOrdering(filters.FilterBackend):
return queryset return queryset
class IssueViewSet(NotificationSenderMixin, ModelCrudViewSet): class IssueViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet):
model = models.Issue
queryset = models.Issue.objects.all().prefetch_related("attachments")
serializer_class = serializers.IssueNeighborsSerializer serializer_class = serializers.IssueNeighborsSerializer
list_serializer_class = serializers.IssueSerializer list_serializer_class = serializers.IssueSerializer
permission_classes = (IsAuthenticated, permissions.IssuePermission) permission_classes = (IsAuthenticated, permissions.IssuePermission)
filter_backends = (filters.IsProjectMemberFilterBackend, IssuesFilter, IssuesOrdering) filter_backends = (filters.IsProjectMemberFilterBackend, IssuesFilter, IssuesOrdering)
retrieve_exclude_filters = (IssuesFilter,) retrieve_exclude_filters = (IssuesFilter,)
filter_fields = ("project",)
order_by_fields = ("severity", "status", "priority", "created_date", "modified_date", "owner",
"assigned_to", "subject")
create_notification_template = "create_issue_notification" filter_fields = ("project",)
update_notification_template = "update_issue_notification" order_by_fields = ("severity",
destroy_notification_template = "destroy_issue_notification" "status",
"priority",
"created_date",
"modified_date",
"owner",
"assigned_to",
"subject")
def get_queryset(self): def get_queryset(self):
qs = self.model.objects.all() qs = models.Issue.objects.all()
qs = qs.prefetch_related("attachments")
qs = attach_votescount_to_queryset(qs, as_field="votes_count") qs = attach_votescount_to_queryset(qs, as_field="votes_count")
return qs return qs

View File

@ -24,11 +24,11 @@ from django.utils.translation import ugettext_lazy as _
from picklefield.fields import PickledObjectField from picklefield.fields import PickledObjectField
from taiga.base.utils.slug import ref_uniquely from taiga.base.utils.slug import ref_uniquely
from taiga.projects.notifications.models import WatchedMixin from taiga.projects.notifications import WatchedModelMixin
from taiga.projects.mixins.blocked import BlockedMixin from taiga.projects.mixins.blocked import BlockedMixin
class Issue(WatchedMixin, BlockedMixin, models.Model): class Issue(WatchedModelMixin, BlockedMixin, models.Model):
ref = models.BigIntegerField(db_index=True, null=True, blank=True, default=None, ref = models.BigIntegerField(db_index=True, null=True, blank=True, default=None,
verbose_name=_("ref")) verbose_name=_("ref"))
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, default=None, owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, default=None,
@ -58,9 +58,6 @@ class Issue(WatchedMixin, BlockedMixin, models.Model):
assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
default=None, related_name="issues_assigned_to_me", default=None, related_name="issues_assigned_to_me",
verbose_name=_("assigned to")) verbose_name=_("assigned to"))
watchers = models.ManyToManyField(settings.AUTH_USER_MODEL, null=True, blank=True,
related_name="watched_issues",
verbose_name=_("watchers"))
tags = PickledObjectField(null=False, blank=True, verbose_name=_("tags")) tags = PickledObjectField(null=False, blank=True, verbose_name=_("tags"))
attachments = generic.GenericRelation("attachments.Attachment") attachments = generic.GenericRelation("attachments.Attachment")
@ -90,14 +87,6 @@ class Issue(WatchedMixin, BlockedMixin, models.Model):
return ", ".join(value) return ", ".join(value)
return value return value
def _get_watchers_by_role(self):
return {
"owner": self.owner,
"assigned_to": self.assigned_to,
"suscribed_watchers": self.watchers.all(),
"project": self.project,
}
# Model related signals handlers # Model related signals handlers
@receiver(models.signals.pre_save, sender=Issue, dispatch_uid="issue_finished_date_handler") @receiver(models.signals.pre_save, sender=Issue, dispatch_uid="issue_finished_date_handler")

View File

@ -18,7 +18,7 @@ from rest_framework import serializers
from taiga.base.serializers import PickleField, NeighborsSerializerMixin from taiga.base.serializers import PickleField, NeighborsSerializerMixin
from taiga.projects.attachments.serializers import AttachmentSerializer from taiga.projects.attachments.serializers import AttachmentSerializer
from taiga.projects.mixins.notifications import WatcherValidationSerializerMixin # from taiga.projects.mixins.notifications import WatcherValidationSerializerMixin
from taiga.mdrender.service import render as mdrender from taiga.mdrender.service import render as mdrender
from . import models from . import models
@ -29,7 +29,9 @@ class IssueAttachmentSerializer(AttachmentSerializer):
fields = ("id", "name", "size", "url", "owner", "created_date", "modified_date", ) fields = ("id", "name", "size", "url", "owner", "created_date", "modified_date", )
class IssueSerializer(WatcherValidationSerializerMixin, serializers.ModelSerializer): # class IssueSerializer(WatcherValidationSerializerMixin, serializers.ModelSerializer):
class IssueSerializer(serializers.ModelSerializer):
tags = PickleField(required=False) tags = PickleField(required=False)
is_closed = serializers.Field(source="is_closed") is_closed = serializers.Field(source="is_closed")
comment = serializers.SerializerMethodField("get_comment") comment = serializers.SerializerMethodField("get_comment")

View File

@ -24,7 +24,10 @@ from taiga.base import filters
from taiga.base import exceptions as exc from taiga.base import exceptions as exc
from taiga.base.decorators import detail_route from taiga.base.decorators import detail_route
from taiga.base.api import ModelCrudViewSet from taiga.base.api import ModelCrudViewSet
from taiga.projects.mixins.notifications import NotificationSenderMixin
from taiga.projects.notifications import WatchedResourceMixin
from taiga.projects.history import HistoryResourceMixin
from . import serializers from . import serializers
from . import models from . import models
@ -33,21 +36,20 @@ from . import permissions
import datetime import datetime
class MilestoneViewSet(NotificationSenderMixin, ModelCrudViewSet): class MilestoneViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet):
# TODO: Refactor this, too much prefetch related
queryset = models.Milestone.objects.all().order_by("-estimated_start").prefetch_related(
"user_stories",
"user_stories__role_points",
"user_stories__role_points__points",
"user_stories__role_points__role",
)
serializer_class = serializers.MilestoneSerializer serializer_class = serializers.MilestoneSerializer
permission_classes = (IsAuthenticated, permissions.MilestonePermission) permission_classes = (IsAuthenticated, permissions.MilestonePermission)
filter_backends = (filters.IsProjectMemberFilterBackend,) filter_backends = (filters.IsProjectMemberFilterBackend,)
filter_fields = ("project",) filter_fields = ("project",)
create_notification_template = "create_milestone_notification"
update_notification_template = "update_milestone_notification" def get_queryset(self):
destroy_notification_template = "destroy_milestone_notification" qs = models.Milestone.objects.all()
qs = qs.prefetch_related("user_stories",
"user_stories__role_points",
"user_stories__role_points__points",
"user_stories__role_points__role")
qs = qs.order_by("-estimated_start")
return qs
def pre_conditions_on_save(self, obj): def pre_conditions_on_save(self, obj):
super().pre_conditions_on_save(obj) super().pre_conditions_on_save(obj)

View File

@ -20,15 +20,14 @@ from django.utils.translation import ugettext_lazy as _
from taiga.base.utils.slug import slugify_uniquely from taiga.base.utils.slug import slugify_uniquely
from taiga.base.utils.dicts import dict_sum from taiga.base.utils.dicts import dict_sum
from taiga.projects.notifications.models import WatchedMixin from taiga.projects.notifications import WatchedModelMixin
from taiga.projects.userstories.models import UserStory from taiga.projects.userstories.models import UserStory
import itertools import itertools
import datetime import datetime
class Milestone(WatchedMixin, models.Model): class Milestone(WatchedModelMixin, models.Model):
name = models.CharField(max_length=200, db_index=True, null=False, blank=False, name = models.CharField(max_length=200, db_index=True, null=False, blank=False,
verbose_name=_("name")) verbose_name=_("name"))
# TODO: Change the unique restriction to a unique together with the project id # TODO: Change the unique restriction to a unique together with the project id
@ -124,12 +123,6 @@ class Milestone(WatchedMixin, models.Model):
def shared_increment_points(self): def shared_increment_points(self):
return self._get_points_increment(True, True) return self._get_points_increment(True, True)
def _get_watchers_by_role(self):
return {
"owner": self.owner,
"project": self.project,
}
def closed_points_by_date(self, date): def closed_points_by_date(self, date):
return self._get_user_stories_points([ return self._get_user_stories_points([
us for us in self.user_stories.filter( us for us in self.user_stories.filter(

View File

@ -26,27 +26,26 @@ from taiga.base import exceptions as exc
from taiga.base.decorators import list_route from taiga.base.decorators import list_route
from taiga.base.permissions import has_project_perm from taiga.base.permissions import has_project_perm
from taiga.base.api import ModelCrudViewSet from taiga.base.api import ModelCrudViewSet
from taiga.projects.mixins.notifications import NotificationSenderMixin
from taiga.projects.models import Project from taiga.projects.models import Project
from taiga.projects.userstories.models import UserStory from taiga.projects.userstories.models import UserStory
from taiga.projects.notifications import WatchedResourceMixin
from taiga.projects.history import HistoryResourceMixin
from . import models from . import models
from . import permissions from . import permissions
from . import serializers from . import serializers
from . import services from . import services
class TaskViewSet(NotificationSenderMixin, ModelCrudViewSet): class TaskViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet):
model = models.Task model = models.Task
serializer_class = serializers.TaskSerializer serializer_class = serializers.TaskSerializer
permission_classes = (IsAuthenticated, permissions.TaskPermission) permission_classes = (IsAuthenticated, permissions.TaskPermission)
filter_backends = (filters.IsProjectMemberFilterBackend,) filter_backends = (filters.IsProjectMemberFilterBackend,)
filter_fields = ["user_story", "milestone", "project"] filter_fields = ["user_story", "milestone", "project"]
create_notification_template = "create_task_notification"
update_notification_template = "update_task_notification"
destroy_notification_template = "destroy_task_notification"
def pre_save(self, obj): def pre_save(self, obj):
if obj.user_story: if obj.user_story:
obj.milestone = obj.user_story.milestone obj.milestone = obj.user_story.milestone

View File

@ -24,13 +24,13 @@ from django.utils.translation import ugettext_lazy as _
from picklefield.fields import PickledObjectField from picklefield.fields import PickledObjectField
from taiga.base.utils.slug import ref_uniquely from taiga.base.utils.slug import ref_uniquely
from taiga.projects.notifications.models import WatchedMixin from taiga.projects.notifications import WatchedModelMixin
from taiga.projects.userstories.models import UserStory from taiga.projects.userstories.models import UserStory
from taiga.projects.milestones.models import Milestone from taiga.projects.milestones.models import Milestone
from taiga.projects.mixins.blocked import BlockedMixin from taiga.projects.mixins.blocked import BlockedMixin
class Task(WatchedMixin, BlockedMixin): class Task(WatchedModelMixin, BlockedMixin, models.Model):
user_story = models.ForeignKey("userstories.UserStory", null=True, blank=True, user_story = models.ForeignKey("userstories.UserStory", null=True, blank=True,
related_name="tasks", verbose_name=_("user story")) related_name="tasks", verbose_name=_("user story"))
ref = models.BigIntegerField(db_index=True, null=True, blank=True, default=None, ref = models.BigIntegerField(db_index=True, null=True, blank=True, default=None,
@ -56,8 +56,6 @@ class Task(WatchedMixin, BlockedMixin):
assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
default=None, related_name="tasks_assigned_to_me", default=None, related_name="tasks_assigned_to_me",
verbose_name=_("assigned to")) verbose_name=_("assigned to"))
watchers = models.ManyToManyField(settings.AUTH_USER_MODEL, null=True, blank=True,
related_name="watched_tasks", verbose_name=_("watchers"))
tags = PickledObjectField(null=False, blank=True, verbose_name=_("tags")) tags = PickledObjectField(null=False, blank=True, verbose_name=_("tags"))
attachments = generic.GenericRelation("attachments.Attachment") attachments = generic.GenericRelation("attachments.Attachment")
is_iocaine = models.BooleanField(default=False, null=False, blank=True, is_iocaine = models.BooleanField(default=False, null=False, blank=True,
@ -85,15 +83,6 @@ class Task(WatchedMixin, BlockedMixin):
return ", ".join(value) return ", ".join(value)
return value return value
def _get_watchers_by_role(self):
return {
"owner": self.owner,
"assigned_to": self.assigned_to,
"suscribed_watchers": self.watchers.all(),
"project": self.project,
}
def us_has_open_tasks(us, exclude_task): def us_has_open_tasks(us, exclude_task):
qs = us.tasks.all() qs = us.tasks.all()

View File

@ -29,7 +29,9 @@ from taiga.base.decorators import action
from taiga.base.permissions import has_project_perm from taiga.base.permissions import has_project_perm
from taiga.base.api import ModelCrudViewSet from taiga.base.api import ModelCrudViewSet
from taiga.projects.mixins.notifications import NotificationSenderMixin from taiga.projects.notifications import WatchedResourceMixin
from taiga.projects.history import HistoryResourceMixin
from taiga.projects.models import Project from taiga.projects.models import Project
from taiga.projects.history.services import take_snapshot from taiga.projects.history.services import take_snapshot
@ -39,7 +41,7 @@ from . import serializers
from . import services from . import services
class UserStoryViewSet(NotificationSenderMixin, ModelCrudViewSet): class UserStoryViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet):
model = models.UserStory model = models.UserStory
serializer_class = serializers.UserStoryNeighborsSerializer serializer_class = serializers.UserStoryNeighborsSerializer
list_serializer_class = serializers.UserStorySerializer list_serializer_class = serializers.UserStorySerializer
@ -49,16 +51,17 @@ class UserStoryViewSet(NotificationSenderMixin, ModelCrudViewSet):
retrieve_exclude_filters = (filters.TagsFilter,) retrieve_exclude_filters = (filters.TagsFilter,)
filter_fields = ['project', 'milestone', 'milestone__isnull', 'status'] filter_fields = ['project', 'milestone', 'milestone__isnull', 'status']
create_notification_template = "create_userstory_notification"
update_notification_template = "update_userstory_notification"
destroy_notification_template = "destroy_userstory_notification"
# Specific filter used for filtering neighbor user stories # Specific filter used for filtering neighbor user stories
_neighbor_tags_filter = filters.TagsFilter('neighbor_tags') _neighbor_tags_filter = filters.TagsFilter('neighbor_tags')
def get_queryset(self): def get_queryset(self):
return self.model.objects.prefetch_related("points", "role_points", "role_points__points", "role_points__role").select_related("milestone", "project") qs = self.model.objects.all()
# TODO: Refactor this qs = qs.prefetch_related("points",
"role_points",
"role_points__points",
"role_points__role")
qs = qs.select_related("milestone", "project")
return qs
@list_route(methods=["POST"]) @list_route(methods=["POST"])
def bulk_create(self, request, **kwargs): def bulk_create(self, request, **kwargs):
@ -119,7 +122,11 @@ class UserStoryViewSet(NotificationSenderMixin, ModelCrudViewSet):
comment = _("Generate the user story [US #{ref} - " comment = _("Generate the user story [US #{ref} - "
"{subject}](:us:{ref} \"US #{ref} - {subject}\")") "{subject}](:us:{ref} \"US #{ref} - {subject}\")")
comment = comment.format(ref=self.object.ref, subject=self.object.subject) comment = comment.format(ref=self.object.ref, subject=self.object.subject)
take_snapshot(self.object.generated_from_issue, comment=comment, user=self.request.user) history = take_snapshot(self.object.generated_from_issue,
comment=comment,
user=self.request.user)
self.send_notifications(self.object.generated_from_issue, history)
return response return response

View File

@ -23,7 +23,7 @@ from django.utils.translation import ugettext_lazy as _
from picklefield.fields import PickledObjectField from picklefield.fields import PickledObjectField
from taiga.base.utils.slug import ref_uniquely from taiga.base.utils.slug import ref_uniquely
from taiga.projects.notifications.models import WatchedMixin from taiga.projects.notifications import WatchedModelMixin
from taiga.projects.mixins.blocked import BlockedMixin from taiga.projects.mixins.blocked import BlockedMixin
@ -51,8 +51,7 @@ class RolePoints(models.Model):
return "{}: {}".format(self.role.name, self.points.name) return "{}: {}".format(self.role.name, self.points.name)
class UserStory(WatchedMixin, BlockedMixin, models.Model): class UserStory(WatchedModelMixin, BlockedMixin, models.Model):
ref = models.BigIntegerField(db_index=True, null=True, blank=True, default=None, ref = models.BigIntegerField(db_index=True, null=True, blank=True, default=None,
verbose_name=_("ref")) verbose_name=_("ref"))
milestone = models.ForeignKey("milestones.Milestone", null=True, blank=True, milestone = models.ForeignKey("milestones.Milestone", null=True, blank=True,
@ -84,8 +83,6 @@ class UserStory(WatchedMixin, BlockedMixin, models.Model):
assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
default=None, related_name="userstories_assigned_to_me", default=None, related_name="userstories_assigned_to_me",
verbose_name=_("assigned to")) verbose_name=_("assigned to"))
watchers = models.ManyToManyField(settings.AUTH_USER_MODEL, null=True, blank=True,
related_name="watched_user_stories", verbose_name=_("watchers"))
client_requirement = models.BooleanField(default=False, null=False, blank=True, client_requirement = models.BooleanField(default=False, null=False, blank=True,
verbose_name=_("is client requirement")) verbose_name=_("is client requirement"))
team_requirement = models.BooleanField(default=False, null=False, blank=True, team_requirement = models.BooleanField(default=False, null=False, blank=True,
@ -137,18 +134,8 @@ class UserStory(WatchedMixin, BlockedMixin, models.Model):
if isinstance(value, models.manager.Manager): if isinstance(value, models.manager.Manager):
return ", ".join(["{}: {}".format(rp.role.name, rp.points.name) return ", ".join(["{}: {}".format(rp.role.name, rp.points.name)
for rp in self.role_points.all().order_by("role")]) for rp in self.role_points.all().order_by("role")])
return None return None
def _get_watchers_by_role(self):
return {
"owner": self.owner,
"assigned_to": self.assigned_to,
"suscribed_watchers": self.watchers.all(),
"project": self.project,
}
@receiver(models.signals.post_save, sender=UserStory, @receiver(models.signals.post_save, sender=UserStory,
dispatch_uid="user_story_create_role_points_handler") dispatch_uid="user_story_create_role_points_handler")

View File

@ -25,26 +25,24 @@ from taiga.base import filters
from taiga.base import exceptions as exc from taiga.base import exceptions as exc
from taiga.base.api import ModelCrudViewSet from taiga.base.api import ModelCrudViewSet
from taiga.base.decorators import list_route from taiga.base.decorators import list_route
from taiga.projects.mixins.notifications import NotificationSenderMixin
from taiga.projects.attachments.api import BaseAttachmentViewSet
from taiga.projects.models import Project from taiga.projects.models import Project
from taiga.mdrender.service import render as mdrender from taiga.mdrender.service import render as mdrender
from taiga.projects.notifications import WatchedResourceMixin
from taiga.projects.history import HistoryResourceMixin
from . import models from . import models
from . import permissions from . import permissions
from . import serializers from . import serializers
class WikiViewSet(NotificationSenderMixin, ModelCrudViewSet): class WikiViewSet(HistoryResourceMixin, WatchedResourceMixin, ModelCrudViewSet):
model = models.WikiPage model = models.WikiPage
serializer_class = serializers.WikiPageSerializer serializer_class = serializers.WikiPageSerializer
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
filter_backends = (filters.IsProjectMemberFilterBackend,) filter_backends = (filters.IsProjectMemberFilterBackend,)
filter_fields = ["project", "slug"] filter_fields = ("project", "slug")
create_notification_template = "create_wiki_notification"
update_notification_template = "update_wiki_notification"
destroy_notification_template = "destroy_wiki_notification"
@list_route(methods=["POST"]) @list_route(methods=["POST"])
def render(self, request, **kwargs): def render(self, request, **kwargs):

View File

@ -18,10 +18,10 @@ from django.db import models
from django.contrib.contenttypes import generic from django.contrib.contenttypes import generic
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from taiga.projects.notifications.models import WatchedMixin from taiga.projects.notifications import WatchedModelMixin
class WikiPage(WatchedMixin, models.Model): class WikiPage(WatchedModelMixin, models.Model):
project = models.ForeignKey("projects.Project", null=False, blank=False, project = models.ForeignKey("projects.Project", null=False, blank=False,
related_name="wiki_pages", verbose_name=_("project")) related_name="wiki_pages", verbose_name=_("project"))
slug = models.SlugField(max_length=500, db_index=True, null=False, blank=False, slug = models.SlugField(max_length=500, db_index=True, null=False, blank=False,
@ -30,9 +30,6 @@ class WikiPage(WatchedMixin, models.Model):
verbose_name=_("content")) verbose_name=_("content"))
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True,
related_name="owned_wiki_pages", verbose_name=_("owner")) related_name="owned_wiki_pages", verbose_name=_("owner"))
watchers = models.ManyToManyField(settings.AUTH_USER_MODEL, null=True, blank=True,
related_name="watched_wiki_pages",
verbose_name=_("watchers"))
created_date = models.DateTimeField(auto_now_add=True, null=False, blank=False, created_date = models.DateTimeField(auto_now_add=True, null=False, blank=False,
verbose_name=_("created date")) verbose_name=_("created date"))
modified_date = models.DateTimeField(auto_now=True, null=False, blank=False, modified_date = models.DateTimeField(auto_now=True, null=False, blank=False,
@ -51,14 +48,6 @@ class WikiPage(WatchedMixin, models.Model):
def __str__(self): def __str__(self):
return "project {0} - {1}".format(self.project_id, self.slug) return "project {0} - {1}".format(self.project_id, self.slug)
def _get_watchers_by_role(self):
return {
"owner": self.owner,
"assigned_to": None,
"suscribed_watchers": self.watchers.all(),
"project": self.project,
}
class WikiLink(models.Model): class WikiLink(models.Model):
project = models.ForeignKey("projects.Project", null=False, blank=False, project = models.ForeignKey("projects.Project", null=False, blank=False,

View File

@ -39,9 +39,6 @@ class UserCreationForm(DjangoUserCreationForm):
class UserChangeForm(DjangoUserChangeForm): class UserChangeForm(DjangoUserChangeForm):
notify_level = forms.ChoiceField(choices=User.NOTIFY_LEVEL_CHOICES)
notify_changes_by_me = forms.BooleanField(required=False)
class Meta: class Meta:
model = User model = User

View File

@ -0,0 +1,210 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Deleting field 'User.notify_changes_by_me'
db.delete_column('users_user', 'notify_changes_by_me')
# Deleting field 'User.notify_level'
db.delete_column('users_user', 'notify_level')
def backwards(self, orm):
# Adding field 'User.notify_changes_by_me'
db.add_column('users_user', 'notify_changes_by_me',
self.gf('django.db.models.fields.BooleanField')(default=False),
keep_default=False)
# Adding field 'User.notify_level'
db.add_column('users_user', 'notify_level',
self.gf('django.db.models.fields.CharField')(default='all_owned_projects', max_length=32),
keep_default=False)
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['auth.Permission']", 'blank': 'True'})
},
'auth.permission': {
'Meta': {'object_name': 'Permission', 'unique_together': "(('content_type', 'codename'),)", 'ordering': "('content_type__app_label', 'content_type__model', 'codename')"},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'contenttypes.contenttype': {
'Meta': {'db_table': "'django_content_type'", 'object_name': 'ContentType', 'unique_together': "(('app_label', 'model'),)", 'ordering': "('name',)"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'projects.issuestatus': {
'Meta': {'object_name': 'IssueStatus', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"},
'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'issue_statuses'", 'to': "orm['projects.Project']"})
},
'projects.issuetype': {
'Meta': {'object_name': 'IssueType', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"},
'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'issue_types'", 'to': "orm['projects.Project']"})
},
'projects.membership': {
'Meta': {'object_name': 'Membership', 'unique_together': "(('user', 'project'),)", 'ordering': "['project', 'role']"},
'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'default': 'datetime.datetime.now', 'blank': 'True'}),
'email': ('django.db.models.fields.EmailField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'memberships'", 'to': "orm['projects.Project']"}),
'role': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'memberships'", 'to': "orm['users.Role']"}),
'token': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '60', 'null': 'True', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'memberships'", 'to': "orm['users.User']", 'null': 'True', 'blank': 'True'})
},
'projects.points': {
'Meta': {'object_name': 'Points', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'points'", 'to': "orm['projects.Project']"}),
'value': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True', 'blank': 'True'})
},
'projects.priority': {
'Meta': {'object_name': 'Priority', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"},
'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'priorities'", 'to': "orm['projects.Project']"})
},
'projects.project': {
'Meta': {'object_name': 'Project', 'ordering': "['name']"},
'created_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'creation_template': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'projects'", 'to': "orm['projects.ProjectTemplate']", 'null': 'True', 'blank': 'True'}),
'default_issue_status': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'to': "orm['projects.IssueStatus']", 'blank': 'True', 'unique': 'True', 'on_delete': 'models.SET_NULL', 'null': 'True'}),
'default_issue_type': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'to': "orm['projects.IssueType']", 'blank': 'True', 'unique': 'True', 'on_delete': 'models.SET_NULL', 'null': 'True'}),
'default_points': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'to': "orm['projects.Points']", 'blank': 'True', 'unique': 'True', 'on_delete': 'models.SET_NULL', 'null': 'True'}),
'default_priority': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'to': "orm['projects.Priority']", 'blank': 'True', 'unique': 'True', 'on_delete': 'models.SET_NULL', 'null': 'True'}),
'default_severity': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'to': "orm['projects.Severity']", 'blank': 'True', 'unique': 'True', 'on_delete': 'models.SET_NULL', 'null': 'True'}),
'default_task_status': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'to': "orm['projects.TaskStatus']", 'blank': 'True', 'unique': 'True', 'on_delete': 'models.SET_NULL', 'null': 'True'}),
'default_us_status': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'to': "orm['projects.UserStoryStatus']", 'blank': 'True', 'unique': 'True', 'on_delete': 'models.SET_NULL', 'null': 'True'}),
'description': ('django.db.models.fields.TextField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_backlog_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_issues_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_kanban_activated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_wiki_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'members': ('django.db.models.fields.related.ManyToManyField', [], {'through': "orm['projects.Membership']", 'related_name': "'projects'", 'to': "orm['users.User']", 'symmetrical': 'False'}),
'modified_date': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '250'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'owned_projects'", 'to': "orm['users.User']"}),
'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '250', 'blank': 'True'}),
'tags': ('picklefield.fields.PickledObjectField', [], {'blank': 'True'}),
'total_milestones': ('django.db.models.fields.IntegerField', [], {'default': '0', 'null': 'True', 'blank': 'True'}),
'total_story_points': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True'}),
'videoconferences': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}),
'videoconferences_salt': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'})
},
'projects.projecttemplate': {
'Meta': {'object_name': 'ProjectTemplate', 'ordering': "['name']"},
'created_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'default_options': ('django_pgjson.fields.JsonField', [], {}),
'default_owner_role': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
'description': ('django.db.models.fields.TextField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_backlog_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_issues_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_kanban_activated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_wiki_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'issue_statuses': ('django_pgjson.fields.JsonField', [], {}),
'issue_types': ('django_pgjson.fields.JsonField', [], {}),
'modified_date': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '250'}),
'points': ('django_pgjson.fields.JsonField', [], {}),
'priorities': ('django_pgjson.fields.JsonField', [], {}),
'roles': ('django_pgjson.fields.JsonField', [], {}),
'severities': ('django_pgjson.fields.JsonField', [], {}),
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '250', 'blank': 'True'}),
'task_statuses': ('django_pgjson.fields.JsonField', [], {}),
'us_statuses': ('django_pgjson.fields.JsonField', [], {}),
'videoconferences': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}),
'videoconferences_salt': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'})
},
'projects.severity': {
'Meta': {'object_name': 'Severity', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"},
'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'severities'", 'to': "orm['projects.Project']"})
},
'projects.taskstatus': {
'Meta': {'object_name': 'TaskStatus', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"},
'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_statuses'", 'to': "orm['projects.Project']"})
},
'projects.userstorystatus': {
'Meta': {'object_name': 'UserStoryStatus', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"},
'color': ('django.db.models.fields.CharField', [], {'default': "'#999999'", 'max_length': '20'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'us_statuses'", 'to': "orm['projects.Project']"}),
'wip_limit': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'null': 'True', 'blank': 'True'})
},
'users.role': {
'Meta': {'object_name': 'Role', 'unique_together': "(('slug', 'project'),)", 'ordering': "['order', 'slug']"},
'computable': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'roles'", 'to': "orm['auth.Permission']", 'symmetrical': 'False'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'roles'", 'to': "orm['projects.Project']"}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '250', 'blank': 'True'})
},
'users.user': {
'Meta': {'object_name': 'User', 'ordering': "['username']"},
'color': ('django.db.models.fields.CharField', [], {'default': "'#e6748a'", 'max_length': '9', 'blank': 'True'}),
'colorize_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'default_language': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '20', 'blank': 'True'}),
'default_timezone': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '20', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'user_set'", 'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'photo': ('django.db.models.fields.files.FileField', [], {'max_length': '500', 'null': 'True', 'blank': 'True'}),
'token': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '200', 'null': 'True', 'blank': 'True'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'user_set'", 'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
}
}
complete_apps = ['users']

View File

@ -20,7 +20,6 @@ from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import UserManager, AbstractUser from django.contrib.auth.models import UserManager, AbstractUser
from taiga.base.utils.slug import slugify_uniquely from taiga.base.utils.slug import slugify_uniquely
from taiga.projects.notifications.models import WatcherMixin
import random import random
@ -29,7 +28,7 @@ def generate_random_hex_color():
return "#{:06x}".format(random.randint(0,0xFFFFFF)) return "#{:06x}".format(random.randint(0,0xFFFFFF))
class User(AbstractUser, WatcherMixin): class User(AbstractUser):
color = models.CharField(max_length=9, null=False, blank=True, default=generate_random_hex_color, color = models.CharField(max_length=9, null=False, blank=True, default=generate_random_hex_color,
verbose_name=_("color")) verbose_name=_("color"))
description = models.TextField(null=False, blank=True, description = models.TextField(null=False, blank=True,

View File

@ -26,17 +26,17 @@ from .models import User, Role
class PermissionSerializer(serializers.ModelSerializer): class PermissionSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Permission model = Permission
fields = ['id', 'name', 'codename'] fields = ("id", "name", "codename")
class UserSerializer(serializers.ModelSerializer): class UserSerializer(serializers.ModelSerializer):
full_name = serializers.CharField(source='get_full_name', required=False) full_name = serializers.CharField(source="get_full_name", required=False)
class Meta: class Meta:
model = User model = User
fields = ('id', 'username', 'first_name', 'last_name', 'full_name', 'email', fields = ("id", "username", "first_name", "last_name", "full_name", "email",
'color', 'description', 'default_language', 'default_timezone', "color", "description", "default_language", "default_timezone",
'is_active', 'photo', 'notify_level', 'notify_changes_by_me') "is_active", "photo",)
class RecoverySerializer(serializers.Serializer): class RecoverySerializer(serializers.Serializer):