Refactor attachments
parent
09eced41a0
commit
e8a2b9fbb3
|
@ -15,26 +15,11 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.contenttypes import generic
|
|
||||||
|
|
||||||
from taiga.projects.milestones.admin import MilestoneInline
|
from taiga.projects.milestones.admin import MilestoneInline
|
||||||
from taiga.users.admin import RoleInline
|
from taiga.users.admin import RoleInline
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
import reversion
|
|
||||||
|
|
||||||
|
|
||||||
class AttachmentAdmin(reversion.VersionAdmin):
|
|
||||||
list_display = ["id", "project", "attached_file", "owner", "content_type", "content_object"]
|
|
||||||
list_display_links = ["id", "attached_file",]
|
|
||||||
list_filter = ["project", "content_type"]
|
|
||||||
|
|
||||||
|
|
||||||
class AttachmentInline(generic.GenericTabularInline):
|
|
||||||
model = models.Attachment
|
|
||||||
fields = ("attached_file", "owner")
|
|
||||||
extra = 0
|
|
||||||
|
|
||||||
|
|
||||||
class MembershipAdmin(admin.ModelAdmin):
|
class MembershipAdmin(admin.ModelAdmin):
|
||||||
list_display = ['project', 'role', 'user']
|
list_display = ['project', 'role', 'user']
|
||||||
|
@ -129,7 +114,6 @@ admin.site.register(models.TaskStatus, TaskStatusAdmin)
|
||||||
admin.site.register(models.UserStoryStatus, UserStoryStatusAdmin)
|
admin.site.register(models.UserStoryStatus, UserStoryStatusAdmin)
|
||||||
admin.site.register(models.Points, PointsAdmin)
|
admin.site.register(models.Points, PointsAdmin)
|
||||||
admin.site.register(models.Project, ProjectAdmin)
|
admin.site.register(models.Project, ProjectAdmin)
|
||||||
admin.site.register(models.Attachment, AttachmentAdmin)
|
|
||||||
admin.site.register(models.Membership, MembershipAdmin)
|
admin.site.register(models.Membership, MembershipAdmin)
|
||||||
admin.site.register(models.Severity, SeverityAdmin)
|
admin.site.register(models.Severity, SeverityAdmin)
|
||||||
admin.site.register(models.Priority, PriorityAdmin)
|
admin.site.register(models.Priority, PriorityAdmin)
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
# 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 django.contrib.contenttypes import generic
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
class AttachmentAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ["id", "project", "attached_file", "owner", "content_type", "content_object"]
|
||||||
|
list_display_links = ["id", "attached_file",]
|
||||||
|
list_filter = ["project", "content_type"]
|
||||||
|
|
||||||
|
|
||||||
|
class AttachmentInline(generic.GenericTabularInline):
|
||||||
|
model = models.Attachment
|
||||||
|
fields = ("attached_file", "owner")
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(models.Attachment, AttachmentAdmin)
|
|
@ -0,0 +1,110 @@
|
||||||
|
# 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.utils.translation import ugettext as _
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
|
||||||
|
from taiga.base.api import ModelCrudViewSet
|
||||||
|
from taiga.base import filters
|
||||||
|
from taiga.base import exceptions as exc
|
||||||
|
from taiga.projects.mixins.notifications import NotificationSenderMixin
|
||||||
|
from taiga.projects.history.services import take_snapshot
|
||||||
|
|
||||||
|
from . import permissions
|
||||||
|
from . import serializers
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAttachmentViewSet(NotificationSenderMixin, ModelCrudViewSet):
|
||||||
|
model = models.Attachment
|
||||||
|
serializer_class = serializers.AttachmentSerializer
|
||||||
|
permission_classes = (IsAuthenticated, permissions.AttachmentPermission,)
|
||||||
|
|
||||||
|
filter_backends = (filters.IsProjectMemberFilterBackend,)
|
||||||
|
filter_fields = ["project", "object_id"]
|
||||||
|
|
||||||
|
content_type = None
|
||||||
|
|
||||||
|
def get_content_type(self):
|
||||||
|
app_name, model = self.content_type.split(".", 1)
|
||||||
|
return get_object_or_404(ContentType, app_label=app_name, model=model)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
ct = self.get_content_type()
|
||||||
|
qs = super().get_queryset()
|
||||||
|
qs = qs.filter(content_type=ct)
|
||||||
|
return qs.distinct()
|
||||||
|
|
||||||
|
def pre_save(self, obj):
|
||||||
|
if not obj.id:
|
||||||
|
obj.content_type = self.get_content_type()
|
||||||
|
obj.owner = self.request.user
|
||||||
|
|
||||||
|
super().pre_save(obj)
|
||||||
|
|
||||||
|
def pre_conditions_on_save(self, obj):
|
||||||
|
super().pre_conditions_on_save(obj)
|
||||||
|
|
||||||
|
if (obj.project.owner != self.request.user and
|
||||||
|
obj.project.memberships.filter(user=self.request.user).count() == 0):
|
||||||
|
raise exc.PermissionDenied(_("You don't have permissions for "
|
||||||
|
"add attachments to this user story"))
|
||||||
|
|
||||||
|
def _get_object_for_snapshot(self, obj):
|
||||||
|
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):
|
||||||
|
content_type = "userstories.userstory"
|
||||||
|
create_notification_template = "create_userstory_notification"
|
||||||
|
update_notification_template = "update_userstory_notification"
|
||||||
|
destroy_notification_template = "destroy_userstory_notification"
|
||||||
|
|
||||||
|
|
||||||
|
class IssueAttachmentViewSet(BaseAttachmentViewSet):
|
||||||
|
content_type = "issues.issue"
|
||||||
|
create_notification_template = "create_issue_notification"
|
||||||
|
update_notification_template = "update_issue_notification"
|
||||||
|
destroy_notification_template = "destroy_issue_notification"
|
||||||
|
|
||||||
|
|
||||||
|
class TaskAttachmentViewSet(BaseAttachmentViewSet):
|
||||||
|
content_type = "tasks.task"
|
||||||
|
create_notification_template = "create_task_notification"
|
||||||
|
update_notification_template = "update_task_notification"
|
||||||
|
destroy_notification_template = "destroy_task_notification"
|
||||||
|
|
||||||
|
|
||||||
|
class WikiAttachmentViewSet(BaseAttachmentViewSet):
|
||||||
|
content_type = "wiki.wiki"
|
||||||
|
create_notification_template = "create_wiki_notification"
|
||||||
|
update_notification_template = "update_wiki_notification"
|
||||||
|
destroy_notification_template = "destroy_wiki_notification"
|
|
@ -0,0 +1,246 @@
|
||||||
|
# -*- 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):
|
||||||
|
db.rename_table('projects_attachment', 'attachments_attachment')
|
||||||
|
|
||||||
|
if not db.dry_run:
|
||||||
|
# For permissions to work properly after migrating
|
||||||
|
orm['contenttypes.contenttype'].objects.filter(app_label='projects', model='attachment').update(app_label='attachments')
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
db.rename_table('attachments_attachment', 'projects_attachment', )
|
||||||
|
|
||||||
|
if not db.dry_run:
|
||||||
|
# For permissions to work properly after migrating
|
||||||
|
orm['contenttypes.contenttype'].objects.filter(app_label='attachments', model='attachment').update(app_label='projects')
|
||||||
|
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'attachments.attachment': {
|
||||||
|
'Meta': {'object_name': 'Attachment', 'ordering': "['project', 'created_date']"},
|
||||||
|
'attached_file': ('django.db.models.fields.files.FileField', [], {'null': 'True', 'max_length': '500', 'blank': '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', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||||
|
'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
|
||||||
|
'order': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||||
|
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['users.User']", 'related_name': "'change_attachments'"}),
|
||||||
|
'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']", 'related_name': "'attachments'"})
|
||||||
|
},
|
||||||
|
'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', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'auth.permission': {
|
||||||
|
'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission', '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': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'", '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'})
|
||||||
|
},
|
||||||
|
'domains.domain': {
|
||||||
|
'Meta': {'object_name': 'Domain', 'ordering': "('domain',)"},
|
||||||
|
'alias_of': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['domains.Domain']", 'null': 'True', 'default': 'None', 'blank': 'True', 'related_name': "'+'"}),
|
||||||
|
'default_language': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '20', 'blank': 'True'}),
|
||||||
|
'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', [], {'default': 'None', 'max_length': '60', 'null': 'True'})
|
||||||
|
},
|
||||||
|
'projects.issuestatus': {
|
||||||
|
'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'IssueStatus', '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', [], {'to': "orm['projects.Project']", 'related_name': "'issue_statuses'"})
|
||||||
|
},
|
||||||
|
'projects.issuetype': {
|
||||||
|
'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'IssueType', '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', [], {'to': "orm['projects.Project']", 'related_name': "'issue_types'"})
|
||||||
|
},
|
||||||
|
'projects.membership': {
|
||||||
|
'Meta': {'unique_together': "(('user', 'project', 'email'),)", 'object_name': 'Membership', 'ordering': "['project', 'role']"},
|
||||||
|
'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'blank': 'True', 'auto_now_add': 'True'}),
|
||||||
|
'email': ('django.db.models.fields.EmailField', [], {'default': 'None', 'null': 'True', 'max_length': '255', 'blank': 'True'}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']", 'related_name': "'memberships'"}),
|
||||||
|
'role': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['users.Role']", 'related_name': "'memberships'"}),
|
||||||
|
'token': ('django.db.models.fields.CharField', [], {'default': 'None', 'null': 'True', 'max_length': '60', 'blank': 'True'}),
|
||||||
|
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['users.User']", 'null': 'True', 'default': 'None', 'blank': 'True', 'related_name': "'memberships'"})
|
||||||
|
},
|
||||||
|
'projects.points': {
|
||||||
|
'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'Points', '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', [], {'to': "orm['projects.Project']", 'related_name': "'points'"}),
|
||||||
|
'value': ('django.db.models.fields.FloatField', [], {'default': 'None', 'blank': 'True', 'null': 'True'})
|
||||||
|
},
|
||||||
|
'projects.priority': {
|
||||||
|
'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'Priority', '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', [], {'to': "orm['projects.Project']", 'related_name': "'priorities'"})
|
||||||
|
},
|
||||||
|
'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', [], {'to': "orm['projects.ProjectTemplate']", 'null': 'True', 'default': 'None', 'blank': 'True', 'related_name': "'projects'"}),
|
||||||
|
'default_issue_status': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'on_delete': 'models.SET_NULL', 'to': "orm['projects.IssueStatus']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'default_issue_type': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'on_delete': 'models.SET_NULL', 'to': "orm['projects.IssueType']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'default_points': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'on_delete': 'models.SET_NULL', 'to': "orm['projects.Points']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'default_priority': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'on_delete': 'models.SET_NULL', 'to': "orm['projects.Priority']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'default_question_status': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'on_delete': 'models.SET_NULL', 'to': "orm['projects.QuestionStatus']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'default_severity': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'on_delete': 'models.SET_NULL', 'to': "orm['projects.Severity']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'default_task_status': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'on_delete': 'models.SET_NULL', 'to': "orm['projects.TaskStatus']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'default_us_status': ('django.db.models.fields.related.OneToOneField', [], {'unique': 'True', 'related_name': "'+'", 'on_delete': 'models.SET_NULL', 'to': "orm['projects.UserStoryStatus']", 'blank': 'True', 'null': 'True'}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {}),
|
||||||
|
'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['domains.Domain']", 'null': 'True', 'default': 'None', 'blank': 'True', 'related_name': "'projects'"}),
|
||||||
|
'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', [], {'default': '0', 'null': 'True'}),
|
||||||
|
'last_task_ref': ('django.db.models.fields.BigIntegerField', [], {'default': '0', 'null': 'True'}),
|
||||||
|
'last_us_ref': ('django.db.models.fields.BigIntegerField', [], {'default': '0', 'null': 'True'}),
|
||||||
|
'members': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['users.User']", 'through': "orm['projects.Membership']", 'symmetrical': 'False', 'related_name': "'projects'"}),
|
||||||
|
'modified_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '250'}),
|
||||||
|
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['users.User']", 'related_name': "'owned_projects'"}),
|
||||||
|
'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', 'blank': 'True', 'null': 'True'}),
|
||||||
|
'total_story_points': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True'}),
|
||||||
|
'videoconferences': ('django.db.models.fields.CharField', [], {'null': 'True', 'max_length': '250', 'blank': 'True'}),
|
||||||
|
'videoconferences_salt': ('django.db.models.fields.CharField', [], {'null': 'True', 'max_length': '250', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'projects.projecttemplate': {
|
||||||
|
'Meta': {'unique_together': "(['slug', 'domain'],)", 'object_name': 'ProjectTemplate', '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', [], {'to': "orm['domains.Domain']", 'null': 'True', 'default': 'None', 'blank': 'True', 'related_name': "'templates'"}),
|
||||||
|
'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', [], {'auto_now': 'True', 'blank': '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', [], {'max_length': '250', 'blank': 'True'}),
|
||||||
|
'task_statuses': ('django_pgjson.fields.JsonField', [], {}),
|
||||||
|
'us_statuses': ('django_pgjson.fields.JsonField', [], {}),
|
||||||
|
'videoconferences': ('django.db.models.fields.CharField', [], {'null': 'True', 'max_length': '250', 'blank': 'True'}),
|
||||||
|
'videoconferences_salt': ('django.db.models.fields.CharField', [], {'null': 'True', 'max_length': '250', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'projects.questionstatus': {
|
||||||
|
'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'QuestionStatus', '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', [], {'to': "orm['projects.Project']", 'related_name': "'question_status'"})
|
||||||
|
},
|
||||||
|
'projects.severity': {
|
||||||
|
'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'Severity', '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', [], {'to': "orm['projects.Project']", 'related_name': "'severities'"})
|
||||||
|
},
|
||||||
|
'projects.taskstatus': {
|
||||||
|
'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'TaskStatus', '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', [], {'to': "orm['projects.Project']", 'related_name': "'task_statuses'"})
|
||||||
|
},
|
||||||
|
'projects.userstorystatus': {
|
||||||
|
'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'UserStoryStatus', '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', [], {'to': "orm['projects.Project']", 'related_name': "'us_statuses'"}),
|
||||||
|
'wip_limit': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'blank': 'True', 'null': 'True'})
|
||||||
|
},
|
||||||
|
'users.role': {
|
||||||
|
'Meta': {'unique_together': "(('slug', 'project'),)", 'object_name': 'Role', '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', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'related_name': "'roles'"}),
|
||||||
|
'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']", 'related_name': "'roles'"}),
|
||||||
|
'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': "'#7da64d'", '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', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True', 'related_name': "'user_set'"}),
|
||||||
|
'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'}),
|
||||||
|
'notify_changes_by_me': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'notify_level': ('django.db.models.fields.CharField', [], {'default': "'all_owned_projects'", 'max_length': '32'}),
|
||||||
|
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||||
|
'photo': ('django.db.models.fields.files.FileField', [], {'null': 'True', 'max_length': '500', 'blank': 'True'}),
|
||||||
|
'token': ('django.db.models.fields.CharField', [], {'default': 'None', 'null': 'True', 'max_length': '200', 'blank': 'True'}),
|
||||||
|
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True', 'related_name': "'user_set'"}),
|
||||||
|
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
complete_apps = ['attachments']
|
|
@ -0,0 +1,70 @@
|
||||||
|
# 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/>.
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.contrib.contenttypes import generic
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
|
def get_attachment_file_path(instance, filename):
|
||||||
|
template = "attachment-files/{project}/{model}/{stamp}/{filename}"
|
||||||
|
current_timestamp = int(time.mktime(timezone.now().timetuple()))
|
||||||
|
|
||||||
|
upload_to_path = template.format(stamp=current_timestamp,
|
||||||
|
project=instance.project.slug,
|
||||||
|
model=instance.content_type.model,
|
||||||
|
filename=filename)
|
||||||
|
return upload_to_path
|
||||||
|
|
||||||
|
|
||||||
|
class Attachment(models.Model):
|
||||||
|
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False,
|
||||||
|
related_name="change_attachments",
|
||||||
|
verbose_name=_("owner"))
|
||||||
|
project = models.ForeignKey("projects.Project", null=False, blank=False,
|
||||||
|
related_name="attachments", verbose_name=_("project"))
|
||||||
|
content_type = models.ForeignKey(ContentType, null=False, blank=False,
|
||||||
|
verbose_name=_("content type"))
|
||||||
|
object_id = models.PositiveIntegerField(null=False, blank=False,
|
||||||
|
verbose_name=_("object id"))
|
||||||
|
content_object = generic.GenericForeignKey("content_type", "object_id")
|
||||||
|
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"))
|
||||||
|
|
||||||
|
attached_file = models.FileField(max_length=500, null=True, blank=True,
|
||||||
|
upload_to=get_attachment_file_path,
|
||||||
|
verbose_name=_("attached file"))
|
||||||
|
is_deprecated = models.BooleanField(default=False, verbose_name=_("is deprecated"))
|
||||||
|
description = models.TextField(null=False, blank=True, verbose_name=_("description"))
|
||||||
|
order = models.IntegerField(default=0, null=False, blank=False, verbose_name=_("order"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "attachment"
|
||||||
|
verbose_name_plural = "attachments"
|
||||||
|
ordering = ["project", "created_date"]
|
||||||
|
permissions = (
|
||||||
|
("view_attachment", "Can view attachment"),
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "Attachment: {}".format(self.id)
|
|
@ -0,0 +1,28 @@
|
||||||
|
# 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 AttachmentPermission(BasePermission):
|
||||||
|
get_permission = "view_attachment"
|
||||||
|
post_permission = "add_attachment"
|
||||||
|
put_permission = "change_attachment"
|
||||||
|
patch_permission = "change_attachment"
|
||||||
|
delete_permission = "delete_attachment"
|
||||||
|
safe_methods = ["HEAD", "OPTIONS"]
|
||||||
|
path_to_project = ["project"]
|
|
@ -0,0 +1,50 @@
|
||||||
|
# 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
|
||||||
|
|
||||||
|
from os import path
|
||||||
|
|
||||||
|
|
||||||
|
class AttachmentSerializer(serializers.ModelSerializer):
|
||||||
|
name = serializers.SerializerMethodField("get_name")
|
||||||
|
url = serializers.SerializerMethodField("get_url")
|
||||||
|
size = serializers.SerializerMethodField("get_size")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Attachment
|
||||||
|
fields = ("id", "project", "owner", "name", "attached_file", "size", "url",
|
||||||
|
"description", "is_deprecated", "created_date", "modified_date",
|
||||||
|
"object_id", "order")
|
||||||
|
read_only_fields = ("owner",)
|
||||||
|
|
||||||
|
def get_name(self, obj):
|
||||||
|
if obj.attached_file:
|
||||||
|
return path.basename(obj.attached_file.path)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def get_url(self, obj):
|
||||||
|
return obj.attached_file.url if obj and obj.attached_file else ""
|
||||||
|
|
||||||
|
def get_size(self, obj):
|
||||||
|
if obj.attached_file:
|
||||||
|
try:
|
||||||
|
return obj.attached_file.size
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
return 0
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from taiga.projects.admin import AttachmentInline
|
from taiga.projects.attachments.admin import AttachmentInline
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
|
@ -25,9 +25,6 @@ from rest_framework import filters
|
||||||
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.base.decorators import list_route
|
from taiga.base.decorators import list_route
|
||||||
from taiga.projects.permissions import AttachmentPermission
|
|
||||||
from taiga.projects.serializers import AttachmentSerializer
|
|
||||||
from taiga.projects.models import Attachment
|
|
||||||
from taiga.base.api import ModelCrudViewSet
|
from taiga.base.api import ModelCrudViewSet
|
||||||
from taiga.base.api import NeighborsApiMixin
|
from taiga.base.api import NeighborsApiMixin
|
||||||
from taiga.projects.mixins.notifications import NotificationSenderMixin
|
from taiga.projects.mixins.notifications import NotificationSenderMixin
|
||||||
|
@ -141,29 +138,3 @@ class IssueViewSet(NeighborsApiMixin, NotificationSenderMixin, ModelCrudViewSet)
|
||||||
|
|
||||||
if obj.type and obj.type.project != obj.project:
|
if obj.type and obj.type.project != obj.project:
|
||||||
raise exc.PermissionDenied(_("You don't have permissions for add/modify this issue."))
|
raise exc.PermissionDenied(_("You don't have permissions for add/modify this issue."))
|
||||||
class IssueAttachmentViewSet(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.Issue)
|
|
||||||
qs = super().get_queryset()
|
|
||||||
qs = qs.filter(content_type=ct)
|
|
||||||
return qs.distinct()
|
|
||||||
|
|
||||||
def pre_save(self, obj):
|
|
||||||
if not obj.id:
|
|
||||||
obj.content_type = ContentType.objects.get_for_model(models.Issue)
|
|
||||||
obj.owner = self.request.user
|
|
||||||
super().pre_save(obj)
|
|
||||||
|
|
||||||
def pre_conditions_on_save(self, obj):
|
|
||||||
super().pre_conditions_on_save(obj)
|
|
||||||
|
|
||||||
if (obj.project.owner != self.request.user and
|
|
||||||
obj.project.memberships.filter(user=self.request.user).count() == 0):
|
|
||||||
raise exc.PermissionDenied(_("You don't have permissions for add attachments "
|
|
||||||
"to this issue"))
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ class Issue(NeighborsMixin, WatchedMixin, BlockedMixin):
|
||||||
related_name="watched_issues",
|
related_name="watched_issues",
|
||||||
verbose_name=_("watchers"))
|
verbose_name=_("watchers"))
|
||||||
tags = PickledObjectField(null=False, blank=True, verbose_name=_("tags"))
|
tags = PickledObjectField(null=False, blank=True, verbose_name=_("tags"))
|
||||||
attachments = generic.GenericRelation("projects.Attachment")
|
attachments = generic.GenericRelation("attachments.Attachment")
|
||||||
|
|
||||||
notifiable_fields = [
|
notifiable_fields = [
|
||||||
"subject",
|
"subject",
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from taiga.base.serializers import PickleField, NeighborsSerializerMixin
|
from taiga.base.serializers import PickleField, NeighborsSerializerMixin
|
||||||
from taiga.projects.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 . import models
|
from . import models
|
||||||
|
|
|
@ -30,6 +30,7 @@ from taiga.projects.userstories.models import *
|
||||||
from taiga.projects.tasks.models import *
|
from taiga.projects.tasks.models import *
|
||||||
from taiga.projects.issues.models import *
|
from taiga.projects.issues.models import *
|
||||||
from taiga.projects.wiki.models import *
|
from taiga.projects.wiki.models import *
|
||||||
|
from taiga.projects.attachments.models import *
|
||||||
|
|
||||||
import random
|
import random
|
||||||
import datetime
|
import datetime
|
||||||
|
|
|
@ -0,0 +1,226 @@
|
||||||
|
# -*- 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):
|
||||||
|
depends_on = (
|
||||||
|
('attachments', '0001_create_attachment'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
pass
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'auth.group': {
|
||||||
|
'Meta': {'object_name': 'Group'},
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'unique': 'True'}),
|
||||||
|
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['auth.Permission']", 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'auth.permission': {
|
||||||
|
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'object_name': 'Permission', 'unique_together': "(('content_type', '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': {'ordering': "('name',)", 'object_name': 'ContentType', 'db_table': "'django_content_type'", 'unique_together': "(('app_label', 'model'),)"},
|
||||||
|
'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': {'ordering': "('domain',)", 'object_name': 'Domain'},
|
||||||
|
'alias_of': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['domains.Domain']", 'blank': 'True', 'null': 'True', 'related_name': "'+'"}),
|
||||||
|
'default_language': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '20', 'blank': 'True'}),
|
||||||
|
'domain': ('django.db.models.fields.CharField', [], {'max_length': '255', 'unique': 'True'}),
|
||||||
|
'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', [], {'default': 'None', 'max_length': '60', 'null': 'True'})
|
||||||
|
},
|
||||||
|
'projects.issuestatus': {
|
||||||
|
'Meta': {'ordering': "['project', 'order', 'name']", 'object_name': 'IssueStatus', 'unique_together': "(('project', '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', [], {'to': "orm['projects.Project']", 'related_name': "'issue_statuses'"})
|
||||||
|
},
|
||||||
|
'projects.issuetype': {
|
||||||
|
'Meta': {'ordering': "['project', 'order', 'name']", 'object_name': 'IssueType', 'unique_together': "(('project', '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', [], {'to': "orm['projects.Project']", 'related_name': "'issue_types'"})
|
||||||
|
},
|
||||||
|
'projects.membership': {
|
||||||
|
'Meta': {'ordering': "['project', 'role']", 'object_name': 'Membership', 'unique_together': "(('user', 'project', 'email'),)"},
|
||||||
|
'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
'email': ('django.db.models.fields.EmailField', [], {'default': 'None', 'max_length': '255', 'blank': 'True', 'null': 'True'}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']", 'related_name': "'memberships'"}),
|
||||||
|
'role': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['users.Role']", 'related_name': "'memberships'"}),
|
||||||
|
'token': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '60', 'blank': 'True', 'null': 'True'}),
|
||||||
|
'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['users.User']", 'blank': 'True', 'null': 'True', 'related_name': "'memberships'"})
|
||||||
|
},
|
||||||
|
'projects.points': {
|
||||||
|
'Meta': {'ordering': "['project', 'order', 'name']", 'object_name': 'Points', 'unique_together': "(('project', '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', [], {'to': "orm['projects.Project']", 'related_name': "'points'"}),
|
||||||
|
'value': ('django.db.models.fields.FloatField', [], {'default': 'None', 'blank': 'True', 'null': 'True'})
|
||||||
|
},
|
||||||
|
'projects.priority': {
|
||||||
|
'Meta': {'ordering': "['project', 'order', 'name']", 'object_name': 'Priority', 'unique_together': "(('project', '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', [], {'to': "orm['projects.Project']", 'related_name': "'priorities'"})
|
||||||
|
},
|
||||||
|
'projects.project': {
|
||||||
|
'Meta': {'ordering': "['name']", 'object_name': 'Project'},
|
||||||
|
'created_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||||
|
'creation_template': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['projects.ProjectTemplate']", 'blank': 'True', 'null': 'True', 'related_name': "'projects'"}),
|
||||||
|
'default_issue_status': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['projects.IssueStatus']", 'null': 'True', 'related_name': "'+'", 'unique': 'True', 'blank': 'True', 'on_delete': 'models.SET_NULL'}),
|
||||||
|
'default_issue_type': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['projects.IssueType']", 'null': 'True', 'related_name': "'+'", 'unique': 'True', 'blank': 'True', 'on_delete': 'models.SET_NULL'}),
|
||||||
|
'default_points': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['projects.Points']", 'null': 'True', 'related_name': "'+'", 'unique': 'True', 'blank': 'True', 'on_delete': 'models.SET_NULL'}),
|
||||||
|
'default_priority': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['projects.Priority']", 'null': 'True', 'related_name': "'+'", 'unique': 'True', 'blank': 'True', 'on_delete': 'models.SET_NULL'}),
|
||||||
|
'default_question_status': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['projects.QuestionStatus']", 'null': 'True', 'related_name': "'+'", 'unique': 'True', 'blank': 'True', 'on_delete': 'models.SET_NULL'}),
|
||||||
|
'default_severity': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['projects.Severity']", 'null': 'True', 'related_name': "'+'", 'unique': 'True', 'blank': 'True', 'on_delete': 'models.SET_NULL'}),
|
||||||
|
'default_task_status': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['projects.TaskStatus']", 'null': 'True', 'related_name': "'+'", 'unique': 'True', 'blank': 'True', 'on_delete': 'models.SET_NULL'}),
|
||||||
|
'default_us_status': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['projects.UserStoryStatus']", 'null': 'True', 'related_name': "'+'", 'unique': 'True', 'blank': 'True', 'on_delete': 'models.SET_NULL'}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {}),
|
||||||
|
'domain': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['domains.Domain']", 'blank': 'True', 'null': 'True', 'related_name': "'projects'"}),
|
||||||
|
'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', [], {'default': '0', 'null': 'True'}),
|
||||||
|
'last_task_ref': ('django.db.models.fields.BigIntegerField', [], {'default': '0', 'null': 'True'}),
|
||||||
|
'last_us_ref': ('django.db.models.fields.BigIntegerField', [], {'default': '0', 'null': 'True'}),
|
||||||
|
'members': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['users.User']", 'through': "orm['projects.Membership']", 'related_name': "'projects'"}),
|
||||||
|
'modified_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '250', 'unique': 'True'}),
|
||||||
|
'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['users.User']", 'related_name': "'owned_projects'"}),
|
||||||
|
'public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '250', 'unique': 'True', 'blank': 'True'}),
|
||||||
|
'tags': ('picklefield.fields.PickledObjectField', [], {'blank': 'True'}),
|
||||||
|
'total_milestones': ('django.db.models.fields.IntegerField', [], {'default': '0', 'blank': 'True', 'null': 'True'}),
|
||||||
|
'total_story_points': ('django.db.models.fields.FloatField', [], {'default': 'None', 'null': 'True'}),
|
||||||
|
'videoconferences': ('django.db.models.fields.CharField', [], {'max_length': '250', 'blank': 'True', 'null': 'True'}),
|
||||||
|
'videoconferences_salt': ('django.db.models.fields.CharField', [], {'max_length': '250', 'blank': 'True', 'null': 'True'})
|
||||||
|
},
|
||||||
|
'projects.projecttemplate': {
|
||||||
|
'Meta': {'ordering': "['name']", 'object_name': 'ProjectTemplate', 'unique_together': "(['slug', 'domain'],)"},
|
||||||
|
'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', [], {}),
|
||||||
|
'domain': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['domains.Domain']", 'blank': 'True', 'null': 'True', 'related_name': "'templates'"}),
|
||||||
|
'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', [], {'auto_now': 'True', 'blank': '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', [], {'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', 'blank': 'True', 'null': 'True'}),
|
||||||
|
'videoconferences_salt': ('django.db.models.fields.CharField', [], {'max_length': '250', 'blank': 'True', 'null': 'True'})
|
||||||
|
},
|
||||||
|
'projects.questionstatus': {
|
||||||
|
'Meta': {'ordering': "['project', 'order', 'name']", 'object_name': 'QuestionStatus', 'unique_together': "(('project', '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', [], {'to': "orm['projects.Project']", 'related_name': "'question_status'"})
|
||||||
|
},
|
||||||
|
'projects.severity': {
|
||||||
|
'Meta': {'ordering': "['project', 'order', 'name']", 'object_name': 'Severity', 'unique_together': "(('project', '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', [], {'to': "orm['projects.Project']", 'related_name': "'severities'"})
|
||||||
|
},
|
||||||
|
'projects.taskstatus': {
|
||||||
|
'Meta': {'ordering': "['project', 'order', 'name']", 'object_name': 'TaskStatus', 'unique_together': "(('project', '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', [], {'to': "orm['projects.Project']", 'related_name': "'task_statuses'"})
|
||||||
|
},
|
||||||
|
'projects.userstorystatus': {
|
||||||
|
'Meta': {'ordering': "['project', 'order', 'name']", 'object_name': 'UserStoryStatus', 'unique_together': "(('project', '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', [], {'to': "orm['projects.Project']", 'related_name': "'us_statuses'"}),
|
||||||
|
'wip_limit': ('django.db.models.fields.IntegerField', [], {'default': 'None', 'blank': 'True', 'null': 'True'})
|
||||||
|
},
|
||||||
|
'users.role': {
|
||||||
|
'Meta': {'ordering': "['order', 'slug']", 'object_name': 'Role', 'unique_together': "(('slug', 'project'),)"},
|
||||||
|
'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', 'to': "orm['auth.Permission']", 'related_name': "'roles'"}),
|
||||||
|
'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']", 'related_name': "'roles'"}),
|
||||||
|
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '250', 'blank': 'True'})
|
||||||
|
},
|
||||||
|
'users.user': {
|
||||||
|
'Meta': {'ordering': "['username']", 'object_name': 'User'},
|
||||||
|
'color': ('django.db.models.fields.CharField', [], {'default': "'#910f4d'", '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', [], {'symmetrical': 'False', 'to': "orm['auth.Group']", 'blank': 'True', 'related_name': "'user_set'"}),
|
||||||
|
'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'}),
|
||||||
|
'notify_changes_by_me': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'notify_level': ('django.db.models.fields.CharField', [], {'default': "'all_owned_projects'", 'max_length': '32'}),
|
||||||
|
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||||
|
'photo': ('django.db.models.fields.files.FileField', [], {'max_length': '500', 'blank': 'True', 'null': 'True'}),
|
||||||
|
'token': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '200', 'blank': 'True', 'null': 'True'}),
|
||||||
|
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['auth.Permission']", 'blank': 'True', 'related_name': "'user_set'"}),
|
||||||
|
'username': ('django.db.models.fields.CharField', [], {'max_length': '30', 'unique': 'True'})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
complete_apps = ['projects']
|
|
@ -15,16 +15,12 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
import time
|
|
||||||
import reversion
|
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.loading import get_model
|
from django.db.models.loading import get_model
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.contrib.contenttypes import generic
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
@ -279,52 +275,6 @@ class Project(ProjectDefaults, models.Model):
|
||||||
return self._get_user_stories_points(self.user_stories.filter(milestone__isnull=False))
|
return self._get_user_stories_points(self.user_stories.filter(milestone__isnull=False))
|
||||||
|
|
||||||
|
|
||||||
def get_attachment_file_path(instance, filename):
|
|
||||||
template = "attachment-files/{project}/{model}/{stamp}/{filename}"
|
|
||||||
current_timestamp = int(time.mktime(timezone.now().timetuple()))
|
|
||||||
|
|
||||||
upload_to_path = template.format(stamp=current_timestamp,
|
|
||||||
project=instance.project.slug,
|
|
||||||
model=instance.content_type.model,
|
|
||||||
filename=filename)
|
|
||||||
return upload_to_path
|
|
||||||
|
|
||||||
|
|
||||||
class Attachment(models.Model):
|
|
||||||
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=False, blank=False,
|
|
||||||
related_name="change_attachments",
|
|
||||||
verbose_name=_("owner"))
|
|
||||||
project = models.ForeignKey("Project", null=False, blank=False,
|
|
||||||
related_name="attachments", verbose_name=_("project"))
|
|
||||||
content_type = models.ForeignKey(ContentType, null=False, blank=False,
|
|
||||||
verbose_name=_("content type"))
|
|
||||||
object_id = models.PositiveIntegerField(null=False, blank=False,
|
|
||||||
verbose_name=_("object id"))
|
|
||||||
content_object = generic.GenericForeignKey("content_type", "object_id")
|
|
||||||
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"))
|
|
||||||
|
|
||||||
attached_file = models.FileField(max_length=500, null=True, blank=True,
|
|
||||||
upload_to=get_attachment_file_path,
|
|
||||||
verbose_name=_("attached file"))
|
|
||||||
is_deprecated = models.BooleanField(default=False, verbose_name=_("is deprecated"))
|
|
||||||
description = models.TextField(null=False, blank=True, verbose_name=_("description"))
|
|
||||||
order = models.IntegerField(default=0, null=False, blank=False, verbose_name=_("order"))
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = "attachment"
|
|
||||||
verbose_name_plural = "attachments"
|
|
||||||
ordering = ["project", "created_date"]
|
|
||||||
permissions = (
|
|
||||||
("view_attachment", "Can view attachment"),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "Attachment: {}".format(self.id)
|
|
||||||
|
|
||||||
|
|
||||||
# User Stories common Models
|
# User Stories common Models
|
||||||
class UserStoryStatus(models.Model):
|
class UserStoryStatus(models.Model):
|
||||||
name = models.CharField(max_length=255, null=False, blank=False,
|
name = models.CharField(max_length=255, null=False, blank=False,
|
||||||
|
|
|
@ -61,16 +61,6 @@ class MembershipPermission(BasePermission):
|
||||||
path_to_project = ["project"]
|
path_to_project = ["project"]
|
||||||
|
|
||||||
|
|
||||||
class AttachmentPermission(BasePermission):
|
|
||||||
get_permission = "view_attachment"
|
|
||||||
post_permission = "add_attachment"
|
|
||||||
put_permission = "change_attachment"
|
|
||||||
patch_permission = "change_attachment"
|
|
||||||
delete_permission = "delete_attachment"
|
|
||||||
safe_methods = ["HEAD", "OPTIONS"]
|
|
||||||
path_to_project = ["project"]
|
|
||||||
|
|
||||||
|
|
||||||
# User Stories
|
# User Stories
|
||||||
|
|
||||||
class PointsPermission(BasePermission):
|
class PointsPermission(BasePermission):
|
||||||
|
|
|
@ -24,35 +24,6 @@ from taiga.users.models import Role
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
class AttachmentSerializer(serializers.ModelSerializer):
|
|
||||||
name = serializers.SerializerMethodField("get_name")
|
|
||||||
url = serializers.SerializerMethodField("get_url")
|
|
||||||
size = serializers.SerializerMethodField("get_size")
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = models.Attachment
|
|
||||||
fields = ("id", "project", "owner", "name", "attached_file", "size", "url",
|
|
||||||
"description", "is_deprecated", "created_date", "modified_date",
|
|
||||||
"object_id", "order")
|
|
||||||
read_only_fields = ("owner",)
|
|
||||||
|
|
||||||
def get_name(self, obj):
|
|
||||||
if obj.attached_file:
|
|
||||||
return path.basename(obj.attached_file.path)
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def get_url(self, obj):
|
|
||||||
return obj.attached_file.url if obj and obj.attached_file else ""
|
|
||||||
|
|
||||||
def get_size(self, obj):
|
|
||||||
if obj.attached_file:
|
|
||||||
try:
|
|
||||||
return obj.attached_file.size
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
# User Stories common serializers
|
# User Stories common serializers
|
||||||
|
|
||||||
class PointsSerializer(serializers.ModelSerializer):
|
class PointsSerializer(serializers.ModelSerializer):
|
||||||
|
|
|
@ -15,9 +15,8 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
import reversion
|
|
||||||
|
|
||||||
from taiga.projects.admin import AttachmentInline
|
from taiga.projects.attachments.admin import AttachmentInline
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
@ -27,41 +26,13 @@ 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.permissions import AttachmentPermission
|
|
||||||
from taiga.projects.serializers import AttachmentSerializer
|
|
||||||
from taiga.projects.models import Attachment, Project
|
|
||||||
from taiga.projects.mixins.notifications import NotificationSenderMixin
|
from taiga.projects.mixins.notifications import NotificationSenderMixin
|
||||||
|
from taiga.projects.models import Project
|
||||||
from taiga.projects.userstories.models import UserStory
|
from taiga.projects.userstories.models import UserStory
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
from . import permissions
|
from . import permissions
|
||||||
from . import serializers
|
from . import serializers
|
||||||
class TaskAttachmentViewSet(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.Task)
|
|
||||||
qs = super().get_queryset()
|
|
||||||
qs = qs.filter(content_type=ct)
|
|
||||||
return qs.distinct()
|
|
||||||
|
|
||||||
def pre_save(self, obj):
|
|
||||||
if not obj.id:
|
|
||||||
obj.content_type = ContentType.objects.get_for_model(models.Task)
|
|
||||||
obj.owner = self.request.user
|
|
||||||
super().pre_save(obj)
|
|
||||||
|
|
||||||
def pre_conditions_on_save(self, obj):
|
|
||||||
super().pre_conditions_on_save(obj)
|
|
||||||
|
|
||||||
if (obj.project.owner != self.request.user and
|
|
||||||
obj.project.memberships.filter(user=self.request.user).count() == 0):
|
|
||||||
raise exc.PermissionDenied(_("You don't have permissions for add "
|
|
||||||
"attachments to this task."))
|
|
||||||
from . import services
|
from . import services
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ class Task(WatchedMixin, BlockedMixin):
|
||||||
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_tasks", verbose_name=_("watchers"))
|
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("projects.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,
|
||||||
verbose_name=_("is iocaine"))
|
verbose_name=_("is iocaine"))
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,6 @@ from taiga.base.serializers import PickleField
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
import reversion
|
|
||||||
|
|
||||||
|
|
||||||
class TaskSerializer(serializers.ModelSerializer):
|
class TaskSerializer(serializers.ModelSerializer):
|
||||||
tags = PickleField(required=False, default=[])
|
tags = PickleField(required=False, default=[])
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from taiga.projects.admin import AttachmentInline
|
from taiga.projects.attachments.admin import AttachmentInline
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,7 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
|
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
@ -28,12 +27,10 @@ from taiga.base import exceptions as exc
|
||||||
from taiga.base.decorators import list_route
|
from taiga.base.decorators import list_route
|
||||||
from taiga.base.decorators import action
|
from taiga.base.decorators import action
|
||||||
from taiga.base.permissions import has_project_perm
|
from taiga.base.permissions import has_project_perm
|
||||||
from taiga.projects.permissions import AttachmentPermission
|
|
||||||
from taiga.projects.serializers import AttachmentSerializer
|
|
||||||
from taiga.projects.models import Attachment, Project
|
|
||||||
from taiga.base.api import ModelCrudViewSet
|
from taiga.base.api import ModelCrudViewSet
|
||||||
from taiga.base.api import NeighborsApiMixin
|
from taiga.base.api import NeighborsApiMixin
|
||||||
from taiga.projects.mixins.notifications import NotificationSenderMixin
|
from taiga.projects.mixins.notifications import NotificationSenderMixin
|
||||||
|
from taiga.projects.models import Project
|
||||||
from taiga.projects.history.services import take_snapshot
|
from taiga.projects.history.services import take_snapshot
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
@ -42,35 +39,6 @@ from . import serializers
|
||||||
from . import services
|
from . import services
|
||||||
|
|
||||||
|
|
||||||
class UserStoryAttachmentViewSet(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.UserStory)
|
|
||||||
qs = super().get_queryset()
|
|
||||||
qs = qs.filter(content_type=ct)
|
|
||||||
return qs.distinct()
|
|
||||||
|
|
||||||
def pre_save(self, obj):
|
|
||||||
if not obj.id:
|
|
||||||
obj.content_type = ContentType.objects.get_for_model(models.UserStory)
|
|
||||||
obj.owner = self.request.user
|
|
||||||
|
|
||||||
super().pre_save(obj)
|
|
||||||
|
|
||||||
def pre_conditions_on_save(self, obj):
|
|
||||||
super().pre_conditions_on_save(obj)
|
|
||||||
|
|
||||||
if (obj.project.owner != self.request.user and
|
|
||||||
obj.project.memberships.filter(user=self.request.user).count() == 0):
|
|
||||||
raise exc.PermissionDenied(_("You don't have permissions for "
|
|
||||||
"add attachments to this user story"))
|
|
||||||
|
|
||||||
|
|
||||||
class UserStoryViewSet(NeighborsApiMixin, NotificationSenderMixin, ModelCrudViewSet):
|
class UserStoryViewSet(NeighborsApiMixin, NotificationSenderMixin, ModelCrudViewSet):
|
||||||
model = models.UserStory
|
model = models.UserStory
|
||||||
serializer_class = serializers.UserStoryNeighborsSerializer
|
serializer_class = serializers.UserStoryNeighborsSerializer
|
||||||
|
@ -169,11 +137,3 @@ class UserStoryViewSet(NeighborsApiMixin, NotificationSenderMixin, ModelCrudView
|
||||||
|
|
||||||
if obj.status and obj.status.project != obj.project:
|
if obj.status and obj.status.project != obj.project:
|
||||||
raise exc.PermissionDenied(_("You don't have permissions for add/modify this user story"))
|
raise exc.PermissionDenied(_("You don't have permissions for add/modify this user story"))
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
|
@ -24,8 +24,8 @@ from picklefield.fields import PickledObjectField
|
||||||
|
|
||||||
from taiga.base.models import NeighborsMixin
|
from taiga.base.models import NeighborsMixin
|
||||||
from taiga.base.utils.slug import ref_uniquely
|
from taiga.base.utils.slug import ref_uniquely
|
||||||
from taiga.projects.mixins.blocked.models import BlockedMixin
|
|
||||||
from taiga.projects.notifications.models import WatchedMixin
|
from taiga.projects.notifications.models import WatchedMixin
|
||||||
|
from taiga.projects.mixins.blocked import BlockedMixin
|
||||||
|
|
||||||
|
|
||||||
class RolePoints(models.Model):
|
class RolePoints(models.Model):
|
||||||
|
@ -92,7 +92,7 @@ class UserStory(NeighborsMixin, WatchedMixin, BlockedMixin, models.Model):
|
||||||
verbose_name=_("is team requirement"))
|
verbose_name=_("is team requirement"))
|
||||||
tags = PickledObjectField(null=False, blank=True,
|
tags = PickledObjectField(null=False, blank=True,
|
||||||
verbose_name=_("tags"))
|
verbose_name=_("tags"))
|
||||||
attachments = generic.GenericRelation("projects.Attachment")
|
attachments = generic.GenericRelation("attachments.Attachment")
|
||||||
generated_from_issue = models.ForeignKey("issues.Issue", null=True, blank=True,
|
generated_from_issue = models.ForeignKey("issues.Issue", null=True, blank=True,
|
||||||
related_name="generated_user_stories",
|
related_name="generated_user_stories",
|
||||||
verbose_name=_("generated from issue"))
|
verbose_name=_("generated from issue"))
|
||||||
|
|
|
@ -36,7 +36,7 @@ class WikiPage(models.Model):
|
||||||
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,
|
||||||
verbose_name=_("modified date"))
|
verbose_name=_("modified date"))
|
||||||
attachments = generic.GenericRelation("projects.Attachment")
|
attachments = generic.GenericRelation("attachments.Attachment")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "wiki page"
|
verbose_name = "wiki page"
|
||||||
|
|
|
@ -74,6 +74,16 @@ router.register(r"priorities", PriorityViewSet, base_name="priorities")
|
||||||
router.register(r"severities",SeverityViewSet , base_name="severities")
|
router.register(r"severities",SeverityViewSet , base_name="severities")
|
||||||
|
|
||||||
|
|
||||||
|
# Attachments
|
||||||
|
from taiga.projects.attachments.api import UserStoryAttachmentViewSet
|
||||||
|
from taiga.projects.attachments.api import IssueAttachmentViewSet
|
||||||
|
from taiga.projects.attachments.api import TaskAttachmentViewSet
|
||||||
|
from taiga.projects.attachments.api import WikiAttachmentViewSet
|
||||||
|
|
||||||
|
router.register(r"userstories/attachments", UserStoryAttachmentViewSet, base_name="userstory-attachments")
|
||||||
|
router.register(r"tasks/attachments", TaskAttachmentViewSet, base_name="task-attachments")
|
||||||
|
router.register(r"issues/attachments", IssueAttachmentViewSet, base_name="issue-attachments")
|
||||||
|
router.register(r"wiki/attachments", WikiAttachmentViewSet, base_name="wiki-attachments")
|
||||||
|
|
||||||
|
|
||||||
# History & Components
|
# History & Components
|
||||||
|
@ -91,20 +101,12 @@ router.register(r"history/wiki", WikiHistory, base_name="wiki-history")
|
||||||
# Project components
|
# Project components
|
||||||
from taiga.projects.milestones.api import MilestoneViewSet
|
from taiga.projects.milestones.api import MilestoneViewSet
|
||||||
from taiga.projects.userstories.api import UserStoryViewSet
|
from taiga.projects.userstories.api import UserStoryViewSet
|
||||||
from taiga.projects.userstories.api import UserStoryAttachmentViewSet
|
|
||||||
from taiga.projects.tasks.api import TaskViewSet
|
from taiga.projects.tasks.api import TaskViewSet
|
||||||
from taiga.projects.tasks.api import TaskAttachmentViewSet
|
|
||||||
from taiga.projects.issues.api import IssueViewSet
|
from taiga.projects.issues.api import IssueViewSet
|
||||||
from taiga.projects.issues.api import IssueAttachmentViewSet
|
|
||||||
from taiga.projects.wiki.api import WikiViewSet
|
from taiga.projects.wiki.api import WikiViewSet
|
||||||
from taiga.projects.wiki.api import WikiAttachmentViewSet
|
|
||||||
|
|
||||||
router.register(r"milestones", MilestoneViewSet, base_name="milestones")
|
router.register(r"milestones", MilestoneViewSet, base_name="milestones")
|
||||||
router.register(r"userstories", UserStoryViewSet, base_name="userstories")
|
router.register(r"userstories", UserStoryViewSet, base_name="userstories")
|
||||||
router.register(r"userstory-attachments", UserStoryAttachmentViewSet, base_name="userstory-attachments")
|
|
||||||
router.register(r"tasks", TaskViewSet, base_name="tasks")
|
router.register(r"tasks", TaskViewSet, base_name="tasks")
|
||||||
router.register(r"task-attachments", TaskAttachmentViewSet, base_name="task-attachments")
|
|
||||||
router.register(r"issues", IssueViewSet, base_name="issues")
|
router.register(r"issues", IssueViewSet, base_name="issues")
|
||||||
router.register(r"issue-attachments", IssueAttachmentViewSet, base_name="issue-attachments")
|
|
||||||
router.register(r"wiki", WikiViewSet, base_name="wiki")
|
router.register(r"wiki", WikiViewSet, base_name="wiki")
|
||||||
router.register(r"wiki-attachments", WikiAttachmentViewSet, base_name="wiki-attachments")
|
|
||||||
|
|
Loading…
Reference in New Issue