Refactor attachments
parent
09eced41a0
commit
e8a2b9fbb3
|
@ -15,26 +15,11 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.contenttypes import generic
|
||||
|
||||
from taiga.projects.milestones.admin import MilestoneInline
|
||||
from taiga.users.admin import RoleInline
|
||||
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):
|
||||
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.Points, PointsAdmin)
|
||||
admin.site.register(models.Project, ProjectAdmin)
|
||||
admin.site.register(models.Attachment, AttachmentAdmin)
|
||||
admin.site.register(models.Membership, MembershipAdmin)
|
||||
admin.site.register(models.Severity, SeverityAdmin)
|
||||
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 taiga.projects.admin import AttachmentInline
|
||||
from taiga.projects.attachments.admin import AttachmentInline
|
||||
|
||||
from . import models
|
||||
|
||||
|
|
|
@ -25,9 +25,6 @@ from rest_framework import filters
|
|||
from taiga.base import filters
|
||||
from taiga.base import exceptions as exc
|
||||
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 NeighborsApiMixin
|
||||
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:
|
||||
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",
|
||||
verbose_name=_("watchers"))
|
||||
tags = PickledObjectField(null=False, blank=True, verbose_name=_("tags"))
|
||||
attachments = generic.GenericRelation("projects.Attachment")
|
||||
attachments = generic.GenericRelation("attachments.Attachment")
|
||||
|
||||
notifiable_fields = [
|
||||
"subject",
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
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 . import models
|
||||
|
|
|
@ -30,6 +30,7 @@ from taiga.projects.userstories.models import *
|
|||
from taiga.projects.tasks.models import *
|
||||
from taiga.projects.issues.models import *
|
||||
from taiga.projects.wiki.models import *
|
||||
from taiga.projects.attachments.models import *
|
||||
|
||||
import random
|
||||
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/>.
|
||||
|
||||
import itertools
|
||||
import time
|
||||
import reversion
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.db.models.loading import get_model
|
||||
from django.conf import settings
|
||||
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.models import Permission
|
||||
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))
|
||||
|
||||
|
||||
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
|
||||
class UserStoryStatus(models.Model):
|
||||
name = models.CharField(max_length=255, null=False, blank=False,
|
||||
|
|
|
@ -61,16 +61,6 @@ class MembershipPermission(BasePermission):
|
|||
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
|
||||
|
||||
class PointsPermission(BasePermission):
|
||||
|
|
|
@ -24,35 +24,6 @@ from taiga.users.models import Role
|
|||
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
|
||||
|
||||
class PointsSerializer(serializers.ModelSerializer):
|
||||
|
|
|
@ -15,9 +15,8 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.contrib import admin
|
||||
import reversion
|
||||
|
||||
from taiga.projects.admin import AttachmentInline
|
||||
from taiga.projects.attachments.admin import AttachmentInline
|
||||
from . import models
|
||||
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
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.permissions import has_project_perm
|
||||
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.models import Project
|
||||
from taiga.projects.userstories.models import UserStory
|
||||
|
||||
from . import models
|
||||
from . import permissions
|
||||
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
|
||||
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ class Task(WatchedMixin, BlockedMixin):
|
|||
watchers = models.ManyToManyField(settings.AUTH_USER_MODEL, null=True, blank=True,
|
||||
related_name="watched_tasks", verbose_name=_("watchers"))
|
||||
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,
|
||||
verbose_name=_("is iocaine"))
|
||||
|
||||
|
|
|
@ -20,8 +20,6 @@ from taiga.base.serializers import PickleField
|
|||
|
||||
from . import models
|
||||
|
||||
import reversion
|
||||
|
||||
|
||||
class TaskSerializer(serializers.ModelSerializer):
|
||||
tags = PickleField(required=False, default=[])
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
from django.contrib import admin
|
||||
|
||||
from taiga.projects.admin import AttachmentInline
|
||||
from taiga.projects.attachments.admin import AttachmentInline
|
||||
|
||||
from . import models
|
||||
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.db import transaction
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
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 action
|
||||
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 NeighborsApiMixin
|
||||
from taiga.projects.mixins.notifications import NotificationSenderMixin
|
||||
from taiga.projects.models import Project
|
||||
from taiga.projects.history.services import take_snapshot
|
||||
|
||||
from . import models
|
||||
|
@ -42,35 +39,6 @@ from . import serializers
|
|||
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):
|
||||
model = models.UserStory
|
||||
serializer_class = serializers.UserStoryNeighborsSerializer
|
||||
|
@ -169,11 +137,3 @@ class UserStoryViewSet(NeighborsApiMixin, NotificationSenderMixin, ModelCrudView
|
|||
|
||||
if obj.status and obj.status.project != obj.project:
|
||||
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.utils.slug import ref_uniquely
|
||||
from taiga.projects.mixins.blocked.models import BlockedMixin
|
||||
from taiga.projects.notifications.models import WatchedMixin
|
||||
from taiga.projects.mixins.blocked import BlockedMixin
|
||||
|
||||
|
||||
class RolePoints(models.Model):
|
||||
|
@ -92,7 +92,7 @@ class UserStory(NeighborsMixin, WatchedMixin, BlockedMixin, models.Model):
|
|||
verbose_name=_("is team requirement"))
|
||||
tags = PickledObjectField(null=False, blank=True,
|
||||
verbose_name=_("tags"))
|
||||
attachments = generic.GenericRelation("projects.Attachment")
|
||||
attachments = generic.GenericRelation("attachments.Attachment")
|
||||
generated_from_issue = models.ForeignKey("issues.Issue", null=True, blank=True,
|
||||
related_name="generated_user_stories",
|
||||
verbose_name=_("generated from issue"))
|
||||
|
|
|
@ -36,7 +36,7 @@ class WikiPage(models.Model):
|
|||
verbose_name=_("created date"))
|
||||
modified_date = models.DateTimeField(auto_now=True, null=False, blank=False,
|
||||
verbose_name=_("modified date"))
|
||||
attachments = generic.GenericRelation("projects.Attachment")
|
||||
attachments = generic.GenericRelation("attachments.Attachment")
|
||||
|
||||
class Meta:
|
||||
verbose_name = "wiki page"
|
||||
|
|
|
@ -74,6 +74,16 @@ router.register(r"priorities", PriorityViewSet, base_name="priorities")
|
|||
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
|
||||
|
@ -91,20 +101,12 @@ router.register(r"history/wiki", WikiHistory, base_name="wiki-history")
|
|||
# Project components
|
||||
from taiga.projects.milestones.api import MilestoneViewSet
|
||||
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 TaskAttachmentViewSet
|
||||
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 WikiAttachmentViewSet
|
||||
|
||||
router.register(r"milestones", MilestoneViewSet, base_name="milestones")
|
||||
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"task-attachments", TaskAttachmentViewSet, base_name="task-attachments")
|
||||
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-attachments", WikiAttachmentViewSet, base_name="wiki-attachments")
|
||||
|
|
Loading…
Reference in New Issue