From 9389b65157ad24b78560805c30276a5e76fce809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa?= Date: Tue, 18 Sep 2018 13:41:35 +0200 Subject: [PATCH] User settings by project --- settings/common.py | 1 + taiga/projects/settings/__init__.py | 0 taiga/projects/settings/api.py | 62 +++++++++++++++++ taiga/projects/settings/choices.py | 45 +++++++++++++ .../projects/settings/migrations/__init__.py | 0 taiga/projects/settings/models.py | 49 ++++++++++++++ taiga/projects/settings/permissions.py | 28 ++++++++ taiga/projects/settings/serializers.py | 32 +++++++++ taiga/projects/settings/services.py | 67 +++++++++++++++++++ taiga/routers.py | 7 ++ 10 files changed, 291 insertions(+) create mode 100644 taiga/projects/settings/__init__.py create mode 100644 taiga/projects/settings/api.py create mode 100644 taiga/projects/settings/choices.py create mode 100644 taiga/projects/settings/migrations/__init__.py create mode 100644 taiga/projects/settings/models.py create mode 100644 taiga/projects/settings/permissions.py create mode 100644 taiga/projects/settings/serializers.py create mode 100644 taiga/projects/settings/services.py diff --git a/settings/common.py b/settings/common.py index e7c5458b..e0787bfb 100644 --- a/settings/common.py +++ b/settings/common.py @@ -309,6 +309,7 @@ INSTALLED_APPS = [ "taiga.projects.issues", "taiga.projects.wiki", "taiga.projects.contact", + "taiga.projects.settings", "taiga.searches", "taiga.timeline", "taiga.mdrender", diff --git a/taiga/projects/settings/__init__.py b/taiga/projects/settings/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/taiga/projects/settings/api.py b/taiga/projects/settings/api.py new file mode 100644 index 00000000..6ff89e17 --- /dev/null +++ b/taiga/projects/settings/api.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.db.models import Q + +from taiga.base import response +from taiga.base.api import ModelCrudViewSet, ReadOnlyListViewSet + +from taiga.projects.settings.choices import HOMEPAGE_CHOICES +from taiga.projects.models import Project + +from . import models +from . import permissions +from . import serializers +from . import services + + +class UserProjectSettingsViewSet(ModelCrudViewSet): + serializer_class = serializers.UserProjectSettingsSerializer + permission_classes = (permissions.UserProjectSettingsPermission,) + + def _build_user_project_settings(self): + projects = Project.objects.filter( + Q(owner=self.request.user) | + Q(memberships__user=self.request.user) + ).distinct() + + for project in projects: + services.create_user_project_settings_if_not_exists( + project, self.request.user) + + def get_queryset(self): + if self.request.user.is_anonymous(): + return models.UserProjectSettings.objects.none() + + self._build_user_project_settings() + + return models.UserProjectSettings.objects.filter(user=self.request.user)\ + .filter( + Q(project__owner=self.request.user) | + Q(project__memberships__user=self.request.user) + ).distinct() + + +class SectionsViewSet(ReadOnlyListViewSet): + def list(self, request, *args, **kwargs): + return response.Response(HOMEPAGE_CHOICES) diff --git a/taiga/projects/settings/choices.py b/taiga/projects/settings/choices.py new file mode 100644 index 00000000..1e84323b --- /dev/null +++ b/taiga/projects/settings/choices.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import enum +from django.utils.translation import ugettext_lazy as _ + + +class Section(enum.IntEnum): + timeline = 1 + search = 2 + backlog = 3 + kanban = 4 + issues = 5 + wiki = 6 + team = 7 + meetup = 8 + admin = 9 + + +HOMEPAGE_CHOICES = ( + (Section.timeline, _("Timeline")), + (Section.search, _("Search")), + (Section.backlog, _("Backlog")), + (Section.kanban, _("Kanban")), + (Section.issues, _("Issues")), + (Section.wiki, _("TeamWiki")), + (Section.team, _("Team")), + (Section.meetup, _("Meet Up")), + (Section.admin, _("Admin")), +) diff --git a/taiga/projects/settings/migrations/__init__.py b/taiga/projects/settings/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/taiga/projects/settings/models.py b/taiga/projects/settings/models.py new file mode 100644 index 00000000..9343a181 --- /dev/null +++ b/taiga/projects/settings/models.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.conf import settings + +from django.db import models +from django.utils import timezone + +from .choices import HOMEPAGE_CHOICES, Section + + +class UserProjectSettings(models.Model): + """ + This class represents a persistence for + project user notifications preference. + """ + project = models.ForeignKey("projects.Project", related_name="user_project_settings") + user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="user_project_settings") + homepage = models.SmallIntegerField(choices=HOMEPAGE_CHOICES, + default=Section.timeline) + + created_at = models.DateTimeField(default=timezone.now) + modified_at = models.DateTimeField() + _importing = None + + class Meta: + unique_together = ("project", "user",) + ordering = ["created_at"] + + def save(self, *args, **kwargs): + if not self._importing or not self.modified_date: + self.modified_at = timezone.now() + + return super().save(*args, **kwargs) diff --git a/taiga/projects/settings/permissions.py b/taiga/projects/settings/permissions.py new file mode 100644 index 00000000..9d244d66 --- /dev/null +++ b/taiga/projects/settings/permissions.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from taiga.base.api.permissions import (TaigaResourcePermission, IsAuthenticated) + + +class UserProjectSettingsPermission(TaigaResourcePermission): + retrieve_perms = IsAuthenticated() + create_perms = IsAuthenticated() + update_perms = IsAuthenticated() + partial_update_perms = IsAuthenticated() + destroy_perms = IsAuthenticated() + list_perms = IsAuthenticated() diff --git a/taiga/projects/settings/serializers.py b/taiga/projects/settings/serializers.py new file mode 100644 index 00000000..b7175059 --- /dev/null +++ b/taiga/projects/settings/serializers.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from taiga.base.api import serializers + +from . import models + + +class UserProjectSettingsSerializer(serializers.ModelSerializer): + project_name = serializers.SerializerMethodField("get_project_name") + + class Meta: + model = models.UserProjectSettings + fields = ('id', 'project', 'project_name', 'homepage') + + def get_project_name(self, obj): + return obj.project.name diff --git a/taiga/projects/settings/services.py b/taiga/projects/settings/services.py new file mode 100644 index 00000000..179d582c --- /dev/null +++ b/taiga/projects/settings/services.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2014-2017 Andrey Antukh +# Copyright (C) 2014-2017 Jesús Espino +# Copyright (C) 2014-2017 David Barragán +# Copyright (C) 2014-2017 Alejandro Alonso +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from django.apps import apps +from django.db import IntegrityError +from django.utils.translation import ugettext as _ + +from taiga.base import exceptions as exc +from taiga.projects.settings.choices import Section + + +def user_project_settings_exists(project, user) -> bool: + """ + Check if policy exists for specified project + and user. + """ + model_cls = apps.get_model("settings", "UserProjectSettings") + qs = model_cls.objects.filter(project=project, + user=user) + return qs.exists() + + +def create_user_project_settings(project, user, homepage=Section.timeline): + """ + Given a project and user, create notification policy for it. + """ + model_cls = apps.get_model("settings", "UserProjectSettings") + try: + return model_cls.objects.create(project=project, + user=user, + homepage=homepage) + except IntegrityError as e: + raise exc.IntegrityError( + _("Notify exists for specified user and project")) from e + + +def create_user_project_settings_if_not_exists(project, user, + homepage=Section.timeline): + """ + Given a project and user, create notification policy for it. + """ + model_cls = apps.get_model("settings", "UserProjectSettings") + try: + result = model_cls.objects.get_or_create( + project=project, + user=user, + defaults={"homepage": homepage} + ) + return result[0] + except IntegrityError as e: + raise exc.IntegrityError( + _("Notify exists for specified user and project")) from e diff --git a/taiga/routers.py b/taiga/routers.py index b521885f..57a36001 100644 --- a/taiga/routers.py +++ b/taiga/routers.py @@ -49,6 +49,13 @@ from taiga.projects.notifications.api import NotifyPolicyViewSet router.register(r"notify-policies", NotifyPolicyViewSet, base_name="notifications") +# Project settings +from taiga.projects.settings.api import UserProjectSettingsViewSet, SectionsViewSet + +router.register(r"user-project-settings", UserProjectSettingsViewSet, base_name="user-project-settings") +router.register(r"sections", SectionsViewSet, base_name="sections") + + # Projects & Selectors from taiga.projects.api import ProjectViewSet from taiga.projects.api import ProjectFansViewSet