Made a refactor of questions app

remotes/origin/enhancement/email-actions
David Barragán Merino 2013-10-03 19:18:18 +02:00
parent 4620e14028
commit 41277a1f83
5 changed files with 158 additions and 76 deletions

View File

@ -1,8 +1,16 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from rest_framework import generics from django.contrib.contenttypes.models import ContentType
from rest_framework.permissions import IsAuthenticated 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 serializers
from . import models from . import models
from . import permissions from . import permissions
@ -10,27 +18,52 @@ from . import permissions
import reversion import reversion
class QuestionList(generics.ListCreateAPIView): class QuestionStatusViewSet(ModelListViewSet):
model = models.Question model = models.QuestionStatus
serializer_class = serializers.QuestionSerializer serializer_class = serializers.QuestionStatusSerializer
filter_fields = ('project',) permission_classes = (IsAuthenticated, permissions.QuestionStatusPermission)
permission_classes = (IsAuthenticated,) 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): 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): def pre_save(self, obj):
super(QuestionsAttachmentViewSet, self).pre_save(obj)
if not obj.id: if not obj.id:
obj.content_type = ContentType.objects.get_for_model(Question)
obj.owner = self.request.user obj.owner = self.request.user
class QuestionDetail(generics.RetrieveUpdateDestroyAPIView): class QuestionViewSet(NotificationSenderMixin, ModelCrudViewSet):
model = models.Question model = models.Question
serializer_class = serializers.QuestionSerializer 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): def post_save(self, obj, created=False):
with reversion.create_revision(): with reversion.create_revision():
if "comment" in self.request.DATA: if "comment" in self.request.DATA:
# Update the comment in the last version # 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)

View File

@ -7,84 +7,89 @@ from django.utils import timezone
from django.dispatch import receiver from django.dispatch import receiver
from greenmine.base.utils.slug import ref_uniquely from greenmine.base.utils.slug import ref_uniquely
from greenmine.base.notifications.models import WatchedMixin
from picklefield.fields import PickledObjectField from picklefield.fields import PickledObjectField
import reversion
class QuestionStatus(models.Model): class QuestionStatus(models.Model):
name = models.CharField(max_length=255, null=False, blank=False, 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, 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, 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, project = models.ForeignKey("projects.Project", null=False, blank=False,
related_name='question_status', related_name="question_status",
verbose_name=_('project')) verbose_name=_("project"))
class Meta: class Meta:
verbose_name = u'question status' verbose_name = u"question status"
verbose_name_plural = u'question status' verbose_name_plural = u"question status"
ordering = ['project', 'name'] ordering = ["project", "name"]
unique_together = ('project', 'name') unique_together = ("project", "name")
def __unicode__(self): 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, 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,
related_name='owned_questions', related_name="owned_questions", verbose_name=_("owner"))
verbose_name=_('owner')) status = models.ForeignKey("QuestionStatus", null=False, blank=False,
status = models.ForeignKey('QuestionStatus', null=False, blank=False, related_name="questions", verbose_name=_("status"))
related_name='questions',
verbose_name=_('status'))
subject = models.CharField(max_length=250, null=False, blank=False, subject = models.CharField(max_length=250, null=False, blank=False,
verbose_name=_('subject')) verbose_name=_("subject"))
content = models.TextField(null=False, blank=True, content = models.TextField(null=False, blank=True, verbose_name=_("content"))
verbose_name=_('content'))
closed = models.BooleanField(default=False, null=False, blank=True, closed = models.BooleanField(default=False, null=False, blank=True,
verbose_name=_('closed')) verbose_name=_("closed"))
attached_file = models.FileField(max_length=500, null=True, blank=True, project = models.ForeignKey("projects.Project", null=False, blank=False,
upload_to='messages', related_name="questions", verbose_name=_("project"))
verbose_name=_('attached_file')) milestone = models.ForeignKey("milestones.Milestone", null=True, blank=True,
project = models.ForeignKey('projects.Project', null=False, blank=False, default=None, related_name="questions",
related_name='questions', verbose_name=_("milestone"))
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, finished_date = models.DateTimeField(null=True, blank=True,
verbose_name=_('finished date')) verbose_name=_("finished date"))
assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, default=None, assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True,
related_name='questions_assigned_to_me', default=None, related_name="questions_assigned_to_me",
verbose_name=_('assigned_to')) verbose_name=_("assigned_to"))
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_add=True, null=False, blank=False, 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, watchers = models.ManyToManyField(settings.AUTH_USER_MODEL, null=True, blank=True,
related_name='watched_questions', related_name="watched_questions",
verbose_name=_('watchers')) verbose_name=_("watchers"))
tags = PickledObjectField(null=False, blank=True, tags = PickledObjectField(null=False, blank=True, verbose_name=_("tags"))
verbose_name=_('tags'))
notifiable_fields = [
"owner",
"status",
"milestone",
"finished_date",
"subject",
"content",
"assigned_to",
"tags"
]
class Meta: class Meta:
verbose_name = u'question' verbose_name = u"question"
verbose_name_plural = u'questions' verbose_name_plural = u"questions"
ordering = ['project', 'subject', 'id'] ordering = ["project", "created_date", "subject"]
#TODO: permissions unique_together = ("ref", "project")
permissions = ( permissions = (
('reply_question', 'Can reply questions'), ("reply_question", _(u"Can reply questions")),
('change_owned_question', 'Can modify owned questions'), ("change_owned_question", _(u"Can modify owned questions")),
('change_assigned_question', 'Can modify assigned questions'), ("change_assigned_question", _(u"Can modify assigned questions")),
('assign_question_to_other', 'Can assign questions to others'), ("assign_question_to_other", _(u"Can assign questions to others")),
('assign_question_to_myself', 'Can assign questions to myself'), ("assign_question_to_myself", _(u"Can assign questions to myself")),
('change_question_state', 'Can change the question state'), ("change_question_state", _(u"Can change the question state")),
('view_question', 'Can view the question'), ("view_question", _(u"Can view the question")),
) )
def __unicode__(self): def __unicode__(self):
@ -93,8 +98,28 @@ class Question(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self.id: if self.id:
self.modified_date = timezone.now() 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) 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__)

View File

@ -3,11 +3,20 @@
from greenmine.base.permissions import BasePermission 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): class QuestionPermission(BasePermission):
get_permission = "can_view_question" get_permission = "can_view_question"
put_permission = "change_question" put_permission = "change_question"
patch_permission = "change_question" patch_permission = "change_question"
delete_permission = "delete_question" delete_permission = "delete_question"
safe_methods = ["HEAD", "OPTIONS"] safe_methods = ["HEAD", "OPTIONS"]
path_to_project = [] path_to_project = ["project"]

View File

@ -9,16 +9,23 @@ from greenmine.base.serializers import PickleField
from . import models from . import models
class QuestionStatusSerializer(serializers.ModelSerializer):
class Meta:
model = models.QuestionStatus
class QuestionSerializer(serializers.ModelSerializer): class QuestionSerializer(serializers.ModelSerializer):
tags = PickleField() tags = PickleField()
comment = serializers.SerializerMethodField("get_comment") comment = serializers.SerializerMethodField("get_comment")
history = serializers.SerializerMethodField("get_history") history = serializers.SerializerMethodField("get_history")
is_closed = serializers.Field(source="is_closed")
class Meta: class Meta:
model = models.Question model = models.Question
fields = () fields = ()
def get_comment(self, obj): def get_comment(self, obj):
# TODO
return "" return ""
def get_questions_diff(self, old_question_version, new_question_version): def get_questions_diff(self, old_question_version, new_question_version):
@ -49,9 +56,10 @@ class QuestionSerializer(serializers.ModelSerializer):
diff_list = [] diff_list = []
current = None current = None
if obj:
for version in reversed(list(reversion.get_for_object(obj))): for version in reversed(list(reversion.get_for_object(obj))):
if current: if current:
questions_diff = self.get_questions_diff(version, current) questions_diff = self.get_questions_diff(current, version)
diff_list.append(questions_diff) diff_list.append(questions_diff)
current = version current = version

View File

@ -11,6 +11,8 @@ from greenmine.projects.tasks.api import TaskStatusViewSet, TaskViewSet, TasksAt
from greenmine.projects.issues.api import (PriorityViewSet, SeverityViewSet, from greenmine.projects.issues.api import (PriorityViewSet, SeverityViewSet,
IssueStatusViewSet, IssueTypeViewSet, IssueStatusViewSet, IssueTypeViewSet,
IssueViewSet, IssuesAttachmentViewSet,) IssueViewSet, IssuesAttachmentViewSet,)
from greenmine.projects.questions.api import (QuestionStatusViewSet, QuestionViewSet,
QuestionsAttachmentViewSet,)
from greenmine.projects.wiki.api import WikiViewSet 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"issue-attachments", IssuesAttachmentViewSet, base_name="issue-attachments")
router.register(r"issues", IssueViewSet, base_name="issues") 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 # greenmine.projects.wiki
router.register(r"wiki", WikiViewSet, base_name="wiki") router.register(r"wiki", WikiViewSet, base_name="wiki")
#greenmine.projects.questions
# TODO
#greenmine.projects.documents
# TODO