From 41277a1f8309ac58f294d329f08a9a3ad44ca201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Thu, 3 Oct 2013 19:18:18 +0200 Subject: [PATCH] Made a refactor of questions app --- greenmine/projects/questions/api.py | 53 ++++++-- greenmine/projects/questions/models.py | 137 ++++++++++++-------- greenmine/projects/questions/permissions.py | 11 +- greenmine/projects/questions/serializers.py | 18 ++- greenmine/routers.py | 15 ++- 5 files changed, 158 insertions(+), 76 deletions(-) diff --git a/greenmine/projects/questions/api.py b/greenmine/projects/questions/api.py index d8fd1b8f..c7d399bf 100644 --- a/greenmine/projects/questions/api.py +++ b/greenmine/projects/questions/api.py @@ -1,8 +1,16 @@ # -*- coding: utf-8 -*- -from rest_framework import generics +from django.contrib.contenttypes.models import ContentType + from rest_framework.permissions import IsAuthenticated +from greenmine.base import filters +from greenmine.base.api import ModelCrudViewSet, ModelListViewSet +from greenmine.base.notifications.api import NotificationSenderMixin +from greenmine.projects.permissions import AttachmentPermission +from greenmine.projects.serializers import AttachmentSerializer +from greenmine.projects.models import Attachment + from . import serializers from . import models from . import permissions @@ -10,27 +18,52 @@ from . import permissions import reversion -class QuestionList(generics.ListCreateAPIView): - model = models.Question - serializer_class = serializers.QuestionSerializer - filter_fields = ('project',) - permission_classes = (IsAuthenticated,) +class QuestionStatusViewSet(ModelListViewSet): + model = models.QuestionStatus + serializer_class = serializers.QuestionStatusSerializer + permission_classes = (IsAuthenticated, permissions.QuestionStatusPermission) + filter_backends = (filters.IsProjectMemberFilterBackend,) + filter_fields = ("project",) + + +class QuestionsAttachmentViewSet(ModelCrudViewSet): + model = Attachment + serializer_class = AttachmentSerializer + permission_classes = (IsAuthenticated, AttachmentPermission) + filter_backends = (filters.IsProjectMemberFilterBackend,) + filter_fields = ["project", "object_id"] def get_queryset(self): - return super(QuestionList, self).filter(project__members=self.request.user) + ct = ContentType.objects.get_for_model(models.Question) + qs = super(QuestionsAttachmentViewSet, self).get_queryset() + qs = qs.filter(content_type=ct) + return qs.distinct() def pre_save(self, obj): + super(QuestionsAttachmentViewSet, self).pre_save(obj) if not obj.id: + obj.content_type = ContentType.objects.get_for_model(Question) obj.owner = self.request.user -class QuestionDetail(generics.RetrieveUpdateDestroyAPIView): +class QuestionViewSet(NotificationSenderMixin, ModelCrudViewSet): model = models.Question serializer_class = serializers.QuestionSerializer - permission_classes = (IsAuthenticated, permissions.QuestionPermission,) + permission_classes = (IsAuthenticated, permissions.QuestionPermission) + filter_backends = (filters.IsProjectMemberFilterBackend,) + filter_fields = ("project",) + create_notification_template = "create_question_notification" + update_notification_template = "update_question_notification" + destroy_notification_template = "destroy_question_notification" + + def pre_save(self, obj): + super(QuestionViewSet, self).pre_save(obj) + if not obj.id: + obj.owner = self.request.user def post_save(self, obj, created=False): with reversion.create_revision(): if "comment" in self.request.DATA: # Update the comment in the last version - reversion.set_comment(self.request.DATA['comment']) + reversion.set_comment(self.request.DATA["comment"]) + super(QuestionViewSet, self).post_save(obj, created) diff --git a/greenmine/projects/questions/models.py b/greenmine/projects/questions/models.py index ba92d3c8..997b66a2 100644 --- a/greenmine/projects/questions/models.py +++ b/greenmine/projects/questions/models.py @@ -7,84 +7,89 @@ from django.utils import timezone from django.dispatch import receiver from greenmine.base.utils.slug import ref_uniquely +from greenmine.base.notifications.models import WatchedMixin from picklefield.fields import PickledObjectField +import reversion + class QuestionStatus(models.Model): name = models.CharField(max_length=255, null=False, blank=False, - verbose_name=_('name')) + verbose_name=_("name")) order = models.IntegerField(default=10, null=False, blank=False, - verbose_name=_('order')) + verbose_name=_("order")) is_closed = models.BooleanField(default=False, null=False, blank=True, - verbose_name=_('is closed')) + verbose_name=_("is closed")) project = models.ForeignKey("projects.Project", null=False, blank=False, - related_name='question_status', - verbose_name=_('project')) + related_name="question_status", + verbose_name=_("project")) class Meta: - verbose_name = u'question status' - verbose_name_plural = u'question status' - ordering = ['project', 'name'] - unique_together = ('project', 'name') + verbose_name = u"question status" + verbose_name_plural = u"question status" + ordering = ["project", "name"] + unique_together = ("project", "name") def __unicode__(self): - return u'project {0} - {1}'.format(self.project_id, self.name) + return u"project {0} - {1}".format(self.project_id, self.name) -class Question(models.Model): - +class Question(models.Model, WatchedMixin): 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, - related_name='owned_questions', - verbose_name=_('owner')) - status = models.ForeignKey('QuestionStatus', null=False, blank=False, - related_name='questions', - verbose_name=_('status')) + related_name="owned_questions", verbose_name=_("owner")) + status = models.ForeignKey("QuestionStatus", null=False, blank=False, + related_name="questions", verbose_name=_("status")) subject = models.CharField(max_length=250, null=False, blank=False, - verbose_name=_('subject')) - content = models.TextField(null=False, blank=True, - verbose_name=_('content')) + verbose_name=_("subject")) + content = models.TextField(null=False, blank=True, verbose_name=_("content")) closed = models.BooleanField(default=False, null=False, blank=True, - verbose_name=_('closed')) - attached_file = models.FileField(max_length=500, null=True, blank=True, - upload_to='messages', - verbose_name=_('attached_file')) - project = models.ForeignKey('projects.Project', null=False, blank=False, - related_name='questions', - verbose_name=_('project')) - milestone = models.ForeignKey('milestones.Milestone', null=True, blank=True, default=None, - related_name='questions', - verbose_name=_('milestone')) + verbose_name=_("closed")) + project = models.ForeignKey("projects.Project", null=False, blank=False, + related_name="questions", verbose_name=_("project")) + milestone = models.ForeignKey("milestones.Milestone", null=True, blank=True, + default=None, related_name="questions", + verbose_name=_("milestone")) finished_date = models.DateTimeField(null=True, blank=True, - verbose_name=_('finished date')) - assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, default=None, - related_name='questions_assigned_to_me', - verbose_name=_('assigned_to')) + verbose_name=_("finished date")) + assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, + default=None, related_name="questions_assigned_to_me", + verbose_name=_("assigned_to")) 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_add=True, null=False, blank=False, - verbose_name=_('modified date')) + verbose_name=_("modified date")) watchers = models.ManyToManyField(settings.AUTH_USER_MODEL, null=True, blank=True, - related_name='watched_questions', - verbose_name=_('watchers')) - tags = PickledObjectField(null=False, blank=True, - verbose_name=_('tags')) + related_name="watched_questions", + verbose_name=_("watchers")) + tags = PickledObjectField(null=False, blank=True, verbose_name=_("tags")) + + notifiable_fields = [ + "owner", + "status", + "milestone", + "finished_date", + "subject", + "content", + "assigned_to", + "tags" + ] class Meta: - verbose_name = u'question' - verbose_name_plural = u'questions' - ordering = ['project', 'subject', 'id'] - #TODO: permissions + verbose_name = u"question" + verbose_name_plural = u"questions" + ordering = ["project", "created_date", "subject"] + unique_together = ("ref", "project") permissions = ( - ('reply_question', 'Can reply questions'), - ('change_owned_question', 'Can modify owned questions'), - ('change_assigned_question', 'Can modify assigned questions'), - ('assign_question_to_other', 'Can assign questions to others'), - ('assign_question_to_myself', 'Can assign questions to myself'), - ('change_question_state', 'Can change the question state'), - ('view_question', 'Can view the question'), + ("reply_question", _(u"Can reply questions")), + ("change_owned_question", _(u"Can modify owned questions")), + ("change_assigned_question", _(u"Can modify assigned questions")), + ("assign_question_to_other", _(u"Can assign questions to others")), + ("assign_question_to_myself", _(u"Can assign questions to myself")), + ("change_question_state", _(u"Can change the question state")), + ("view_question", _(u"Can view the question")), ) def __unicode__(self): @@ -93,8 +98,28 @@ class Question(models.Model): def save(self, *args, **kwargs): if self.id: self.modified_date = timezone.now() - - if not self.ref: - self.ref = ref_uniquely(self.project, 'last_issue_ref', self.__class__) - super(Question, self).save(*args, **kwargs) + + @property + def is_closed(self): + return self.status.is_closed + + def _get_watchers_by_role(self): + return { + "owner": self.owner, + "assigned_to": self.assigned_to, + "suscribed_watchers": self.watchers.all(), + "project_owner": (self.project, self.project.owner), + } + + +# Reversion registration (usufull for base.notification and for meke a historical) +reversion.register(Question) + + +# Model related signals handlers +@receiver(models.signals.pre_save, sender=Question, dispatch_uid="question_ref_handler") +def question_ref_handler(sender, instance, **kwargs): + if not instance.id and instance.project: + instance.ref = ref_uniquely(instance.project,"last_question_ref", + instance.__class__) diff --git a/greenmine/projects/questions/permissions.py b/greenmine/projects/questions/permissions.py index f0eab228..5bb50f1f 100644 --- a/greenmine/projects/questions/permissions.py +++ b/greenmine/projects/questions/permissions.py @@ -3,11 +3,20 @@ from greenmine.base.permissions import BasePermission +class QuestionStatusPermission(BasePermission): + get_permission = "view_questionstatus" + put_permission = "change_questionstatus" + patch_permission = "change_questionstatus" + delete_permission = "delete_questionstatus" + safe_methods = ["HEAD", "OPTIONS"] + path_to_project = ["project"] + + class QuestionPermission(BasePermission): get_permission = "can_view_question" put_permission = "change_question" patch_permission = "change_question" delete_permission = "delete_question" safe_methods = ["HEAD", "OPTIONS"] - path_to_project = [] + path_to_project = ["project"] diff --git a/greenmine/projects/questions/serializers.py b/greenmine/projects/questions/serializers.py index eaf58e59..928ef20c 100644 --- a/greenmine/projects/questions/serializers.py +++ b/greenmine/projects/questions/serializers.py @@ -9,16 +9,23 @@ from greenmine.base.serializers import PickleField from . import models +class QuestionStatusSerializer(serializers.ModelSerializer): + class Meta: + model = models.QuestionStatus + + class QuestionSerializer(serializers.ModelSerializer): tags = PickleField() comment = serializers.SerializerMethodField("get_comment") history = serializers.SerializerMethodField("get_history") + is_closed = serializers.Field(source="is_closed") class Meta: model = models.Question fields = () def get_comment(self, obj): + # TODO return "" def get_questions_diff(self, old_question_version, new_question_version): @@ -49,11 +56,12 @@ class QuestionSerializer(serializers.ModelSerializer): diff_list = [] current = None - for version in reversed(list(reversion.get_for_object(obj))): - if current: - questions_diff = self.get_questions_diff(version, current) - diff_list.append(questions_diff) + if obj: + for version in reversed(list(reversion.get_for_object(obj))): + if current: + questions_diff = self.get_questions_diff(current, version) + diff_list.append(questions_diff) - current = version + current = version return diff_list diff --git a/greenmine/routers.py b/greenmine/routers.py index 78ed6139..6924564c 100644 --- a/greenmine/routers.py +++ b/greenmine/routers.py @@ -11,6 +11,8 @@ from greenmine.projects.tasks.api import TaskStatusViewSet, TaskViewSet, TasksAt from greenmine.projects.issues.api import (PriorityViewSet, SeverityViewSet, IssueStatusViewSet, IssueTypeViewSet, IssueViewSet, IssuesAttachmentViewSet,) +from greenmine.projects.questions.api import (QuestionStatusViewSet, QuestionViewSet, + QuestionsAttachmentViewSet,) from greenmine.projects.wiki.api import WikiViewSet @@ -48,10 +50,15 @@ router.register(r"issue-types", IssueTypeViewSet, base_name="issue-types") router.register(r"issue-attachments", IssuesAttachmentViewSet, base_name="issue-attachments") router.register(r"issues", IssueViewSet, base_name="issues") +#greenmine.projects.questions +router.register(r"question-statuses", QuestionStatusViewSet, base_name="question-statuses") +router.register(r"question-attachments", QuestionsAttachmentViewSet, + base_name="question-attachments") +router.register(r"questions", QuestionViewSet, base_name="questions") + +#greenmine.projects.documents +# TODO + # greenmine.projects.wiki router.register(r"wiki", WikiViewSet, base_name="wiki") -#greenmine.projects.questions -# TODO -#greenmine.projects.documents -# TODO