Remove code of questions and documents

remotes/origin/enhancement/email-actions
David Barragán Merino 2014-05-09 09:42:14 +02:00
parent 64d9ba17ca
commit 1a9ef0fe04
24 changed files with 257 additions and 638 deletions

View File

@ -183,10 +183,8 @@ INSTALLED_APPS = [
"taiga.projects.userstories",
"taiga.projects.tasks",
"taiga.projects.issues",
"taiga.front",
#"taiga.projects.questions",
#"taiga.projects.documents",
"taiga.projects.wiki",
"taiga.front",
"south",
"reversion",

View File

@ -60,8 +60,7 @@ class ProjectAdmin(reversion.VersionAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if (db_field.name in ["default_points", "default_us_status", "default_task_status",
"default_priority", "default_severity",
"default_issue_status", "default_issue_type",
"default_question_status"]):
"default_issue_status", "default_issue_type"]):
if getattr(self, 'obj', None):
kwargs["queryset"] = db_field.related.parent_model.objects.filter(
project=self.obj)
@ -125,15 +124,6 @@ class IssueStatusAdmin(admin.ModelAdmin):
list_filter = ["project"]
# Questions common admins
class QuestionStatusAdmin(admin.ModelAdmin):
list_display = ["project", "order", "name", "is_closed", "color"]
list_display_links = ["name"]
list_filter = ["project"]
admin.site.register(models.IssueStatus, IssueStatusAdmin)
admin.site.register(models.TaskStatus, TaskStatusAdmin)
admin.site.register(models.UserStoryStatus, UserStoryStatusAdmin)
@ -144,5 +134,3 @@ admin.site.register(models.Membership, MembershipAdmin)
admin.site.register(models.Severity, SeverityAdmin)
admin.site.register(models.Priority, PriorityAdmin)
admin.site.register(models.IssueType, IssueTypeAdmin)
admin.site.register(models.QuestionStatus, QuestionStatusAdmin)

View File

@ -291,16 +291,6 @@ class IssueStatusViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
bulk_update_order = services.bulk_update_issue_status_order
class QuestionStatusViewSet(ModelCrudViewSet, BulkUpdateOrderMixin):
model = models.QuestionStatus
serializer_class = serializers.QuestionStatusSerializer
permission_classes = (IsAuthenticated, permissions.QuestionStatusPermission)
filter_backends = (filters.IsProjectMemberFilterBackend,)
filter_fields = ("project",)
bulk_update_param = "bulk_question_statuses"
bulk_update_perm = "change_questionstatus"
bulk_update_order = services.bulk_update_question_status_order
class ProjectTemplateViewSet(ModelCrudViewSet):
model = models.ProjectTemplate
serializer_class = serializers.ProjectTemplateSerializer

View File

@ -1,25 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 <http://www.gnu.org/licenses/>.
from django.contrib import admin
from . import models
class DocumentAdmin(admin.ModelAdmin):
list_display = ["title", "project", "owner"]
admin.site.register(models.Document, DocumentAdmin)

View File

@ -1,32 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 <http://www.gnu.org/licenses/>.
from rest_framework.permissions import IsAuthenticated
from taiga.base import filters
from taiga.base.api import ModelCrudViewSet
from . import serializers
from . import models
from . import permissions
class DocumentsViewSet(ModelCrudViewSet):
model = models.Document
serializer_class = serializers.DocumentSerializer
permission_classes = (permissions.DocumentPermission,)
filter_backends = (filters.IsProjectMemberFilterBackend,)

View File

@ -1,63 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 <http://www.gnu.org/licenses/>.
from django.db import models
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from picklefield.fields import PickledObjectField
from taiga.base.utils.slug import slugify_uniquely as slugify
class Document(models.Model):
slug = models.SlugField(unique=True, max_length=200, null=False, blank=True,
verbose_name=_("slug"))
title = models.CharField(max_length=150, null=False, blank=False,
verbose_name=_("title"))
description = models.TextField(null=False, blank=True,
verbose_name=_("description"))
created_date = models.DateTimeField(auto_now_add=True, null=False, blank=False,
verbose_name=_("created date"))
modified_date = models.DateTimeField(auto_now=True, null=False, blank=False,
verbose_name=_("modified date"))
project = models.ForeignKey("projects.Project", null=False, blank=False,
related_name="documents",
verbose_name=_("project"))
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False,
related_name="owned_documents",
verbose_name=_("owner"))
attached_file = models.FileField(max_length=1000, null=True, blank=True,
upload_to="documents",
verbose_name=_("attached_file"))
tags = PickledObjectField(null=False, blank=True,
verbose_name=_("tags"))
class Meta:
verbose_name = u"Document"
verbose_name_plural = u"Documents"
ordering = ["project", "title",]
permissions = (
("view_document", "Can view document"),
)
def __str__(self):
return self.title
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title, self.__class__)
super().save(*args, **kwargs)

View File

@ -1,27 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 <http://www.gnu.org/licenses/>.
from taiga.base.permissions import BasePermission
class DocumentPermission(BasePermission):
get_permission = "view_document"
post_permission = "add_document"
put_permission = "change_document"
patch_permission = "change_document"
delete_permission = "delete_document"
safe_methods = ["HEAD", "OPTIONS"]
path_to_project = ["project"]

View File

@ -1,25 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 <http://www.gnu.org/licenses/>.
from rest_framework import serializers
from . import models
class DocumentSerializer(serializers.ModelSerializer):
class Meta:
model = models.Document
fields = ()

View File

@ -29,8 +29,6 @@ from taiga.projects.milestones.models import *
from taiga.projects.userstories.models import *
from taiga.projects.tasks.models import *
from taiga.projects.issues.models import *
#from taiga.projects.questions.models import *
#from taiga.projects.documents.models import *
from taiga.projects.wiki.models import *
import random
@ -153,10 +151,6 @@ class Command(BaseCommand):
for y in range(self.sd.int(15,25)):
bug = self.create_bug(project)
# create questions.
#for y in range(self.sd.int(15,25)):
# question = self.create_question(project)
# create a wiki page
wiki_page = self.create_wiki(project, "home")
@ -184,20 +178,6 @@ class Command(BaseCommand):
return wiki_page
#def create_question(self, project):
# question = Question.objects.create(
# project=project,
# subject=self.sd.choice(SUBJECT_CHOICES),
# content=self.sd.paragraph(),
# owner=self.sd.db_object_from_queryset(project.memberships.all()).user,
# status=self.sd.db_object_from_queryset(project.question_status.all()),
# tags=self.sd.words(1,5).split(" "))
#
# for i in range(self.sd.int(0, 4)):
# attachment = self.create_attachment(question)
#
# return question
def create_bug(self, project):
bug = Issue.objects.create(
project=project,

View File

@ -0,0 +1,253 @@
# -*- 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):
# Removing unique constraint on 'QuestionStatus', fields ['project', 'name']
db.delete_unique('projects_questionstatus', ['project_id', 'name'])
# Deleting model 'QuestionStatus'
db.delete_table('projects_questionstatus')
# Deleting field 'Project.default_question_status'
db.delete_column('projects_project', 'default_question_status_id')
def backwards(self, orm):
# Adding model 'QuestionStatus'
db.create_table('projects_questionstatus', (
('project', self.gf('django.db.models.fields.related.ForeignKey')(related_name='question_status', to=orm['projects.Project'])),
('order', self.gf('django.db.models.fields.IntegerField')(default=10)),
('is_closed', self.gf('django.db.models.fields.BooleanField')(default=False)),
('color', self.gf('django.db.models.fields.CharField')(max_length=20, default='#999999')),
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
))
db.send_create_signal('projects', ['QuestionStatus'])
# Adding unique constraint on 'QuestionStatus', fields ['project', 'name']
db.create_unique('projects_questionstatus', ['project_id', 'name'])
# Adding field 'Project.default_question_status'
db.add_column('projects_project', 'default_question_status',
self.gf('django.db.models.fields.related.OneToOneField')(unique=True, related_name='+', null=True, on_delete=models.SET_NULL, blank=True, to=orm['projects.QuestionStatus']),
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', 'blank': 'True', 'to': "orm['auth.Permission']"})
},
'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': {'object_name': 'ContentType', 'unique_together': "(('app_label', 'model'),)", 'ordering': "('name',)", 'db_table': "'django_content_type'"},
'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'})
},
'domains.domain': {
'Meta': {'object_name': 'Domain', 'ordering': "('domain',)"},
'alias_of': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'blank': 'True', 'to': "orm['domains.Domain']", 'null': 'True', 'default': 'None'}),
'default_language': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '20', 'default': "''"}),
'domain': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'public_register': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scheme': ('django.db.models.fields.CharField', [], {'max_length': '60', 'null': 'True', 'default': 'None'})
},
'projects.attachment': {
'Meta': {'object_name': 'Attachment', 'ordering': "['project', 'created_date']"},
'attached_file': ('django.db.models.fields.files.FileField', [], {'blank': 'True', 'max_length': '500', 'null': 'True'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'created_date': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now_add': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_deprecated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'modified_date': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now': 'True'}),
'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'change_attachments'", 'to': "orm['users.User']"}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attachments'", 'to': "orm['projects.Project']"})
},
'projects.issuestatus': {
'Meta': {'object_name': 'IssueStatus', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"},
'color': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "'#999999'"}),
'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', [], {'max_length': '20', 'default': "'#999999'"}),
'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', 'email'),)", 'ordering': "['project', 'role']"},
'created_at': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now_add': 'True', 'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'blank': 'True', 'max_length': '255', 'null': 'True', 'default': 'None'}),
'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', [], {'blank': 'True', 'max_length': '60', 'null': 'True', 'default': 'None'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'memberships'", 'blank': 'True', 'to': "orm['users.User']", 'null': 'True', 'default': 'None'})
},
'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', [], {'blank': 'True', 'null': 'True', 'default': 'None'})
},
'projects.priority': {
'Meta': {'object_name': 'Priority', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"},
'color': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "'#999999'"}),
'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', [], {'blank': 'True', 'auto_now_add': 'True'}),
'creation_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects'", 'blank': 'True', 'to': "orm['projects.ProjectTemplate']", 'null': 'True', 'default': 'None'}),
'default_issue_status': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True', 'to': "orm['projects.IssueStatus']"}),
'default_issue_type': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True', 'to': "orm['projects.IssueType']"}),
'default_points': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True', 'to': "orm['projects.Points']"}),
'default_priority': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True', 'to': "orm['projects.Priority']"}),
'default_severity': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True', 'to': "orm['projects.Severity']"}),
'default_task_status': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True', 'to': "orm['projects.TaskStatus']"}),
'default_us_status': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True', 'to': "orm['projects.UserStoryStatus']"}),
'description': ('django.db.models.fields.TextField', [], {}),
'domain': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects'", 'blank': 'True', 'to': "orm['domains.Domain']", 'null': 'True', 'default': 'None'}),
'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'}),
'last_issue_ref': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'default': '0'}),
'last_task_ref': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'default': '0'}),
'last_us_ref': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'default': '0'}),
'members': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'projects'", 'to': "orm['users.User']", 'through': "orm['projects.Membership']"}),
'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', 'blank': 'True', 'max_length': '250'}),
'tags': ('picklefield.fields.PickledObjectField', [], {'blank': 'True'}),
'total_milestones': ('django.db.models.fields.IntegerField', [], {'blank': 'True', 'null': 'True', 'default': '0'}),
'total_story_points': ('django.db.models.fields.FloatField', [], {'null': 'True', 'default': 'None'}),
'videoconferences': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '250', 'null': 'True'}),
'videoconferences_salt': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '250', 'null': 'True'})
},
'projects.projecttemplate': {
'Meta': {'object_name': 'ProjectTemplate', 'unique_together': "(['slug', 'domain'],)", 'ordering': "['name']"},
'created_date': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now_add': 'True'}),
'default_options': ('django_pgjson.fields.JsonField', [], {}),
'default_owner_role': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
'description': ('django.db.models.fields.TextField', [], {}),
'domain': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'templates'", 'blank': 'True', 'to': "orm['domains.Domain']", 'null': 'True', 'default': 'None'}),
'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', [], {'blank': 'True', 'max_length': '250'}),
'task_statuses': ('django_pgjson.fields.JsonField', [], {}),
'us_statuses': ('django_pgjson.fields.JsonField', [], {}),
'videoconferences': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '250', 'null': 'True'}),
'videoconferences_salt': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '250', 'null': 'True'})
},
'projects.severity': {
'Meta': {'object_name': 'Severity', 'unique_together': "(('project', 'name'),)", 'ordering': "['project', 'order', 'name']"},
'color': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "'#999999'"}),
'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', [], {'max_length': '20', 'default': "'#999999'"}),
'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', [], {'max_length': '20', 'default': "'#999999'"}),
'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', [], {'blank': 'True', 'null': 'True', 'default': 'None'})
},
'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', [], {'symmetrical': 'False', 'related_name': "'roles'", 'to': "orm['auth.Permission']"}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'roles'", 'to': "orm['projects.Project']"}),
'slug': ('django.db.models.fields.SlugField', [], {'blank': 'True', 'max_length': '250'})
},
'users.user': {
'Meta': {'object_name': 'User', 'ordering': "['username']"},
'color': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '9', 'default': "'#f38bf5'"}),
'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', [], {'blank': 'True', 'max_length': '20', 'default': "''"}),
'default_timezone': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '20', 'default': "''"}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'email': ('django.db.models.fields.EmailField', [], {'blank': 'True', 'max_length': '75'}),
'first_name': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '30'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'user_set'", 'blank': 'True', 'to': "orm['auth.Group']"}),
'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', [], {'blank': 'True', 'max_length': '30'}),
'notify_changes_by_me': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'notify_level': ('django.db.models.fields.CharField', [], {'max_length': '32', 'default': "'all_owned_projects'"}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'photo': ('django.db.models.fields.files.FileField', [], {'blank': 'True', 'max_length': '500', 'null': 'True'}),
'token': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '200', 'null': 'True', 'default': 'None'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'user_set'", 'blank': 'True', 'to': "orm['auth.Permission']"}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
}
}
complete_apps = ['projects']

View File

@ -110,11 +110,6 @@ class ProjectDefaults(models.Model):
on_delete=models.SET_NULL, related_name="+",
null=True, blank=True,
verbose_name=_("default issue type"))
default_question_status = models.OneToOneField("projects.QuestionStatus",
on_delete=models.SET_NULL,
related_name="+", null=True, blank=True,
verbose_name=_("default questions "
"status"))
class Meta:
abstract = True
@ -504,34 +499,6 @@ class IssueType(models.Model):
return self.name
# Questions common models
class QuestionStatus(models.Model):
name = models.CharField(max_length=255, null=False, blank=False,
verbose_name=_("name"))
order = models.IntegerField(default=10, null=False, blank=False,
verbose_name=_("order"))
is_closed = models.BooleanField(default=False, null=False, blank=True,
verbose_name=_("is closed"))
color = models.CharField(max_length=20, null=False, blank=False, default="#999999",
verbose_name=_("color"))
project = models.ForeignKey("Project", null=False, blank=False,
related_name="question_status",
verbose_name=_("project"))
class Meta:
verbose_name = "question status"
verbose_name_plural = "question statuses"
ordering = ["project", "order", "name"]
unique_together = ("project", "name")
permissions = (
("view_questionstatus", "Can view question status"),
)
def __str__(self):
return self.name
class ProjectTemplate(models.Model):
name = models.CharField(max_length=250, null=False, blank=False,
verbose_name=_("name"))

View File

@ -157,16 +157,7 @@ class RolesPermission(BasePermission):
path_to_project = ["project"]
# Questions
class QuestionStatusPermission(BasePermission):
get_permission = "view_questionstatus"
post_permission = "add_questionstatus"
put_permission = "change_questionstatus"
patch_permission = "change_questionstatus"
delete_permission = "delete_questionstatus"
safe_methods = ["HEAD", "OPTIONS"]
path_to_project = ["project"]
# Project Templates
class ProjectTemplatePermission(BasePermission):
def has_permission(self, request, view):

View File

@ -1,53 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 <http://www.gnu.org/licenses/>.
from django.contrib import admin
from taiga.projects.admin import AttachmentInline
from . import models
import reversion
class QuestionAdmin(reversion.VersionAdmin):
list_display = ["project", "milestone", "ref", "subject",]
list_display_links = ["ref", "subject",]
inlines = [AttachmentInline]
def get_object(self, *args, **kwargs):
self.obj = super().get_object(*args, **kwargs)
return self.obj
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if (db_field.name in ["status", "milestone"]
and getattr(self, 'obj', None)):
kwargs["queryset"] = db_field.related.parent_model.objects.filter(
project=self.obj.project)
elif (db_field.name in ["owner", "assigned_to"]
and getattr(self, 'obj', None)):
kwargs["queryset"] = db_field.related.parent_model.objects.filter(
memberships__project=self.obj.project)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
def formfield_for_manytomany(self, db_field, request, **kwargs):
if (db_field.name in ["watchers"]
and getattr(self, 'obj', None)):
kwargs["queryset"] = db_field.related.parent_model.objects.filter(
memberships__project=self.obj.project)
return super().formfield_for_manytomany(db_field, request, **kwargs)
admin.site.register(models.Question, QuestionAdmin)

View File

@ -1,75 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 <http://www.gnu.org/licenses/>.
from django.contrib.contenttypes.models import ContentType
from rest_framework.permissions import IsAuthenticated
from taiga.base import filters
from taiga.base.api import ModelCrudViewSet
from taiga.base.notifications.api import NotificationSenderMixin
from taiga.projects.permissions import AttachmentPermission
from taiga.projects.serializers import AttachmentSerializer
from taiga.projects.models import Attachment
from . import serializers
from . import models
from . import permissions
import reversion
class QuestionAttachmentViewSet(ModelCrudViewSet):
model = Attachment
serializer_class = AttachmentSerializer
permission_classes = (IsAuthenticated, AttachmentPermission)
filter_backends = (filters.IsProjectMemberFilterBackend,)
filter_fields = ["project", "object_id"]
def get_queryset(self):
ct = ContentType.objects.get_for_model(models.Question)
qs = super().get_queryset()
qs = qs.filter(content_type=ct)
return qs.distinct()
def pre_save(self, obj):
super().pre_save(obj)
if not obj.id:
obj.content_type = ContentType.objects.get_for_model(models.Question)
obj.owner = self.request.user
class QuestionViewSet(NotificationSenderMixin, ModelCrudViewSet):
model = models.Question
serializer_class = serializers.QuestionSerializer
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().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"])
super().post_save(obj, created)

View File

@ -1,115 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 <http://www.gnu.org/licenses/>.
from django.db import models
from django.contrib.contenttypes import generic
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from django.utils import timezone
from django.dispatch import receiver
from taiga.base.utils.slug import ref_uniquely
from taiga.base.notifications.models import WatchedMixin
from picklefield.fields import PickledObjectField
import reversion
class Question(WatchedMixin):
ref = models.BigIntegerField(db_index=True, null=True, blank=True, default=None,
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("projects.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"))
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"))
created_date = models.DateTimeField(auto_now_add=True, null=False, blank=False,
verbose_name=_("created date"))
modified_date = models.DateTimeField(auto_now=True, null=False, blank=False,
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"))
attachments = generic.GenericRelation("projects.Attachment")
notifiable_fields = [
"owner",
"status",
"milestone",
"finished_date",
"subject",
"content",
"assigned_to",
"tags"
]
class Meta:
verbose_name = "question"
verbose_name_plural = "questions"
ordering = ["project", "created_date"]
unique_together = ("ref", "project")
permissions = (
("view_question", "Can view question"),
)
def __str__(self):
return self.subject
@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": self.project,
}
# Reversion registration (usufull for base.notification and for meke a historical)
reversion.register(Question)
# Model related signals handlers
@receiver(models.signals.pre_save, sender=Issue, dispatch_uid="question_finished_date_handler")
def question_finished_date_handler(sender, instance, **kwargs):
if instance.status.is_closed and not instance.finished_date:
instance.finished_date = timezone.now()
elif not instance.status.is_closed and instance.finished_date:
instance.finished_date = None
@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

@ -1,28 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 <http://www.gnu.org/licenses/>.
from taiga.base.permissions import BasePermission
class QuestionPermission(BasePermission):
get_permission = "view_question"
post_permission = "add_question"
put_permission = "change_question"
patch_permission = "change_question"
delete_permission = "delete_question"
safe_methods = ["HEAD", "OPTIONS"]
path_to_project = ["project"]

View File

@ -1,76 +0,0 @@
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
# 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 <http://www.gnu.org/licenses/>.
from rest_framework import serializers
import reversion
from taiga.base.serializers import PickleField
from . import models
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):
# NOTE: This method and field is necessary to historical comments work
return ""
def get_questions_diff(self, old_question_version, new_question_version):
old_obj = old_question_version.field_dict
new_obj = new_question_version.field_dict
diff_dict = {
"modified_date": new_obj["modified_date"],
"by": old_question_version.revision.user,
"comment": old_question_version.revision.comment,
}
for key in old_obj.keys():
if key == "modified_date":
continue
if old_obj[key] == new_obj[key]:
continue
diff_dict[key] = {
"old": old_obj[key],
"new": new_obj[key],
}
return diff_dict
def get_history(self, obj):
diff_list = []
current = None
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
return diff_list

View File

@ -94,13 +94,6 @@ class IssueTypeSerializer(serializers.ModelSerializer):
model = models.IssueType
# Questions common serializers
class QuestionStatusSerializer(serializers.ModelSerializer):
class Meta:
model = models.QuestionStatus
# Projects
class MembershipSerializer(serializers.ModelSerializer):
@ -139,7 +132,6 @@ class ProjectDetailSerializer(ProjectSerializer):
severities = SeveritySerializer(many=True, required=False)
issue_statuses = IssueStatusSerializer(many=True, required=False)
issue_types = IssueTypeSerializer(many=True, required=False)
#question_statuses = QuestionStatusSerializer(many=True, required=False) # Questions
def get_active_membership(self, obj):
memberships = obj.memberships.filter(user__isnull=False).order_by('user__first_name', 'user__last_name', 'user__username')

View File

@ -17,7 +17,6 @@
# This makes all code that import services works and
# is not the baddest practice ;)
from .bulk_update_order import bulk_update_question_status_order
from .bulk_update_order import bulk_update_severity_order
from .bulk_update_order import bulk_update_priority_order
from .bulk_update_order import bulk_update_issue_type_order

View File

@ -134,20 +134,3 @@ def bulk_update_severity_order(project, user, data):
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
(order, id, project.id))
cursor.close()
@transaction.atomic
def bulk_update_question_status_order(project, user, data):
cursor = connection.cursor()
sql = """
prepare bulk_update_order as update projects_questionstatus set "order" = $1
where projects_questionstatus.id = $2 and
projects_questionstatus.project_id = $3;
"""
cursor.execute(sql)
for id, order in data:
cursor.execute("EXECUTE bulk_update_order (%s, %s, %s);",
(order, id, project.id))
cursor.close()

View File

@ -23,14 +23,12 @@ from taiga.base.resolver.api import ResolverViewSet
from taiga.projects.api import (ProjectViewSet, MembershipViewSet, InvitationViewSet,
UserStoryStatusViewSet, PointsViewSet, TaskStatusViewSet,
IssueStatusViewSet, IssueTypeViewSet, PriorityViewSet,
SeverityViewSet, ProjectAdminViewSet, RolesViewSet) #, QuestionStatusViewSet)
SeverityViewSet, ProjectAdminViewSet, RolesViewSet)
from taiga.domains.api import DomainViewSet, DomainMembersViewSet
from taiga.projects.milestones.api import MilestoneViewSet
from taiga.projects.userstories.api import UserStoryViewSet, UserStoryAttachmentViewSet
from taiga.projects.tasks.api import TaskViewSet, TaskAttachmentViewSet
from taiga.projects.issues.api import IssueViewSet, IssueAttachmentViewSet
#from taiga.projects.questions.api import QuestionViewSet, QuestionAttachmentViewSet
#from taiga.projects.documents.api import DocumentViewSet, DocumentAttachmentViewSet
from taiga.projects.wiki.api import WikiViewSet, WikiAttachmentViewSet

View File

@ -68,7 +68,6 @@ class PermissionsViewSet(ModelListViewSet):
"add_migrationhistory", "change_migrationhistory", "delete_migrationhistory",
"add_version", "change_version", "delete_version",
"add_revision", "change_revision", "delete_revision",
"add_questionstatus", "change_questionstatus", "delete_questionstatus", "view_questionstatus",
"add_user", "delete_user",
"add_project",
"add_domainmember", "change_domainmember", "delete_domainmember",