Adding colorize tags on server functionality
parent
d6a38261f0
commit
195bdd2523
|
@ -333,3 +333,10 @@ except IndexError:
|
||||||
IN_DEVELOPMENT_SERVER = False
|
IN_DEVELOPMENT_SERVER = False
|
||||||
|
|
||||||
ATTACHMENTS_TOKEN_SALT = "ATTACHMENTS_TOKEN_SALT"
|
ATTACHMENTS_TOKEN_SALT = "ATTACHMENTS_TOKEN_SALT"
|
||||||
|
|
||||||
|
TAGS_PREDEFINED_COLORS = ["#fce94f", "#edd400", "#c4a000", "#8ae234",
|
||||||
|
"#73d216", "#4e9a06", "#d3d7cf", "#fcaf3e",
|
||||||
|
"#f57900", "#ce5c00", "#729fcf", "#3465a4",
|
||||||
|
"#204a87", "#888a85", "#ad7fa8", "#75507b",
|
||||||
|
"#5c3566", "#ef2929", "#cc0000", "#a40000",
|
||||||
|
"#2e3436",]
|
||||||
|
|
|
@ -58,6 +58,19 @@ class PgArrayField(serializers.WritableField):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class TagsColorsField(serializers.WritableField):
|
||||||
|
"""
|
||||||
|
PgArray objects serializer.
|
||||||
|
"""
|
||||||
|
widget = widgets.Textarea
|
||||||
|
|
||||||
|
def to_native(self, obj):
|
||||||
|
return dict(obj)
|
||||||
|
|
||||||
|
def from_native(self, data):
|
||||||
|
return list(data.items())
|
||||||
|
|
||||||
|
|
||||||
class NeighborsSerializerMixin:
|
class NeighborsSerializerMixin:
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
|
@ -8,8 +8,6 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from djorm_pgarray.fields import TextArrayField
|
from djorm_pgarray.fields import TextArrayField
|
||||||
|
|
||||||
from picklefield.fields import PickledObjectField
|
|
||||||
|
|
||||||
|
|
||||||
class TaggedMixin(models.Model):
|
class TaggedMixin(models.Model):
|
||||||
tags = TextArrayField(default=None, verbose_name=_("tags"))
|
tags = TextArrayField(default=None, verbose_name=_("tags"))
|
||||||
|
|
|
@ -26,6 +26,7 @@ from taiga.base.utils.slug import ref_uniquely
|
||||||
from taiga.projects.notifications import WatchedModelMixin
|
from taiga.projects.notifications import WatchedModelMixin
|
||||||
from taiga.projects.occ import OCCModelMixin
|
from taiga.projects.occ import OCCModelMixin
|
||||||
from taiga.projects.mixins.blocked import BlockedMixin
|
from taiga.projects.mixins.blocked import BlockedMixin
|
||||||
|
from taiga.projects.services.tags_colors import update_project_tags_colors_handler, remove_unused_tags
|
||||||
|
|
||||||
|
|
||||||
class Issue(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, models.Model):
|
class Issue(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, models.Model):
|
||||||
|
@ -100,3 +101,14 @@ def issue_finished_date_handler(sender, instance, **kwargs):
|
||||||
def issue_tags_normalization(sender, instance, **kwargs):
|
def issue_tags_normalization(sender, instance, **kwargs):
|
||||||
if isinstance(instance.tags, (list, tuple)):
|
if isinstance(instance.tags, (list, tuple)):
|
||||||
instance.tags = list(map(lambda x: x.lower(), instance.tags))
|
instance.tags = list(map(lambda x: x.lower(), instance.tags))
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(models.signals.post_save, sender=Issue, dispatch_uid="issue_update_project_colors")
|
||||||
|
def issue_update_project_tags(sender, instance, **kwargs):
|
||||||
|
update_project_tags_colors_handler(instance)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(models.signals.post_delete, sender=Issue, dispatch_uid="issue_update_project_colors_on_delete")
|
||||||
|
def issue_update_project_tags_on_delete(sender, instance, **kwargs):
|
||||||
|
remove_unused_tags(instance.project)
|
||||||
|
instance.project.save()
|
||||||
|
|
|
@ -0,0 +1,186 @@
|
||||||
|
# -*- 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):
|
||||||
|
# Adding field 'Project.tags_colors'
|
||||||
|
db.add_column('projects_project', 'tags_colors',
|
||||||
|
self.gf('djorm_pgarray.fields.TextArrayField')(blank=True, default={}, dimension=2, dbtype='text'),
|
||||||
|
keep_default=False)
|
||||||
|
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
# Deleting field 'Project.tags_colors'
|
||||||
|
db.delete_column('projects_project', 'tags_colors')
|
||||||
|
|
||||||
|
|
||||||
|
models = {
|
||||||
|
'projects.issuestatus': {
|
||||||
|
'Meta': {'ordering': "['project', 'order', 'name']", 'object_name': 'IssueStatus', 'unique_together': "(('project', 'name'),)"},
|
||||||
|
'color': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "'#999999'"}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||||
|
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
|
||||||
|
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'issue_statuses'", 'to': "orm['projects.Project']"})
|
||||||
|
},
|
||||||
|
'projects.issuetype': {
|
||||||
|
'Meta': {'ordering': "['project', 'order', 'name']", 'object_name': 'IssueType', 'unique_together': "(('project', 'name'),)"},
|
||||||
|
'color': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "'#999999'"}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||||
|
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
|
||||||
|
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'issue_types'", 'to': "orm['projects.Project']"})
|
||||||
|
},
|
||||||
|
'projects.membership': {
|
||||||
|
'Meta': {'ordering': "['project', 'user__full_name', 'user__username', 'user__email', 'email']", 'object_name': 'Membership', 'unique_together': "(('user', 'project'),)"},
|
||||||
|
'created_at': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now_add': 'True', 'default': 'datetime.datetime.now'}),
|
||||||
|
'email': ('django.db.models.fields.EmailField', [], {'blank': 'True', 'max_length': '255', 'null': 'True', 'default': 'None'}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'invited_by_id': ('django.db.models.fields.IntegerField', [], {'blank': 'True', 'null': 'True'}),
|
||||||
|
'is_owner': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'memberships'", 'to': "orm['projects.Project']"}),
|
||||||
|
'role': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'memberships'", 'to': "orm['users.Role']"}),
|
||||||
|
'token': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '60', 'null': 'True', 'default': 'None'}),
|
||||||
|
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'null': 'True', 'default': 'None', 'to': "orm['users.User']", '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', [], {'related_name': "'points'", 'to': "orm['projects.Project']"}),
|
||||||
|
'value': ('django.db.models.fields.FloatField', [], {'blank': 'True', 'null': 'True', 'default': 'None'})
|
||||||
|
},
|
||||||
|
'projects.priority': {
|
||||||
|
'Meta': {'ordering': "['project', 'order', 'name']", 'object_name': 'Priority', 'unique_together': "(('project', 'name'),)"},
|
||||||
|
'color': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "'#999999'"}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||||
|
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
|
||||||
|
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'priorities'", 'to': "orm['projects.Project']"})
|
||||||
|
},
|
||||||
|
'projects.project': {
|
||||||
|
'Meta': {'ordering': "['name']", 'object_name': 'Project'},
|
||||||
|
'anon_permissions': ('djorm_pgarray.fields.TextArrayField', [], {'blank': 'True', 'null': 'True', 'default': '[]', 'dbtype': "'text'"}),
|
||||||
|
'created_date': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now_add': 'True'}),
|
||||||
|
'creation_template': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'null': 'True', 'default': 'None', 'to': "orm['projects.ProjectTemplate']", 'related_name': "'projects'"}),
|
||||||
|
'default_issue_status': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'on_delete': 'models.SET_NULL', 'blank': 'True', 'null': 'True', 'to': "orm['projects.IssueStatus']", 'unique': 'True'}),
|
||||||
|
'default_issue_type': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'on_delete': 'models.SET_NULL', 'blank': 'True', 'null': 'True', 'to': "orm['projects.IssueType']", 'unique': 'True'}),
|
||||||
|
'default_points': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'on_delete': 'models.SET_NULL', 'blank': 'True', 'null': 'True', 'to': "orm['projects.Points']", 'unique': 'True'}),
|
||||||
|
'default_priority': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'on_delete': 'models.SET_NULL', 'blank': 'True', 'null': 'True', 'to': "orm['projects.Priority']", 'unique': 'True'}),
|
||||||
|
'default_severity': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'on_delete': 'models.SET_NULL', 'blank': 'True', 'null': 'True', 'to': "orm['projects.Severity']", 'unique': 'True'}),
|
||||||
|
'default_task_status': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'on_delete': 'models.SET_NULL', 'blank': 'True', 'null': 'True', 'to': "orm['projects.TaskStatus']", 'unique': 'True'}),
|
||||||
|
'default_us_status': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'+'", 'on_delete': 'models.SET_NULL', 'blank': 'True', 'null': 'True', 'to': "orm['projects.UserStoryStatus']", 'unique': 'True'}),
|
||||||
|
'description': ('django.db.models.fields.TextField', [], {}),
|
||||||
|
'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_private': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'is_wiki_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'members': ('django.db.models.fields.related.ManyToManyField', [], {'through': "orm['projects.Membership']", 'related_name': "'projects'", 'to': "orm['users.User']", 'symmetrical': 'False'}),
|
||||||
|
'modified_date': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '250', 'unique': 'True'}),
|
||||||
|
'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'owned_projects'", 'to': "orm['users.User']"}),
|
||||||
|
'public_permissions': ('djorm_pgarray.fields.TextArrayField', [], {'blank': 'True', 'null': 'True', 'default': '[]', 'dbtype': "'text'"}),
|
||||||
|
'slug': ('django.db.models.fields.SlugField', [], {'blank': 'True', 'max_length': '250', 'unique': 'True'}),
|
||||||
|
'tags': ('djorm_pgarray.fields.TextArrayField', [], {'blank': 'True', 'null': 'True', 'default': 'None', 'dbtype': "'text'"}),
|
||||||
|
'tags_colors': ('djorm_pgarray.fields.TextArrayField', [], {'blank': 'True', 'default': '{}', 'dimension': '2', 'dbtype': "'text'"}),
|
||||||
|
'total_milestones': ('django.db.models.fields.IntegerField', [], {'blank': 'True', 'null': 'True', 'default': '0'}),
|
||||||
|
'total_story_points': ('django.db.models.fields.FloatField', [], {'default': '0'}),
|
||||||
|
'videoconferences': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '250', 'null': 'True'}),
|
||||||
|
'videoconferences_salt': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '250', 'null': 'True'})
|
||||||
|
},
|
||||||
|
'projects.projecttemplate': {
|
||||||
|
'Meta': {'ordering': "['name']", 'object_name': 'ProjectTemplate'},
|
||||||
|
'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', [], {}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'is_backlog_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'is_issues_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'is_kanban_activated': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'is_wiki_activated': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'issue_statuses': ('django_pgjson.fields.JsonField', [], {}),
|
||||||
|
'issue_types': ('django_pgjson.fields.JsonField', [], {}),
|
||||||
|
'modified_date': ('django.db.models.fields.DateTimeField', [], {'blank': 'True', 'auto_now': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '250'}),
|
||||||
|
'points': ('django_pgjson.fields.JsonField', [], {}),
|
||||||
|
'priorities': ('django_pgjson.fields.JsonField', [], {}),
|
||||||
|
'roles': ('django_pgjson.fields.JsonField', [], {}),
|
||||||
|
'severities': ('django_pgjson.fields.JsonField', [], {}),
|
||||||
|
'slug': ('django.db.models.fields.SlugField', [], {'blank': 'True', 'max_length': '250', 'unique': 'True'}),
|
||||||
|
'task_statuses': ('django_pgjson.fields.JsonField', [], {}),
|
||||||
|
'us_statuses': ('django_pgjson.fields.JsonField', [], {}),
|
||||||
|
'videoconferences': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '250', 'null': 'True'}),
|
||||||
|
'videoconferences_salt': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '250', 'null': 'True'})
|
||||||
|
},
|
||||||
|
'projects.severity': {
|
||||||
|
'Meta': {'ordering': "['project', 'order', 'name']", 'object_name': 'Severity', 'unique_together': "(('project', 'name'),)"},
|
||||||
|
'color': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "'#999999'"}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||||
|
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
|
||||||
|
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'severities'", 'to': "orm['projects.Project']"})
|
||||||
|
},
|
||||||
|
'projects.taskstatus': {
|
||||||
|
'Meta': {'ordering': "['project', 'order', 'name']", 'object_name': 'TaskStatus', 'unique_together': "(('project', 'name'),)"},
|
||||||
|
'color': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "'#999999'"}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||||
|
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
|
||||||
|
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'task_statuses'", 'to': "orm['projects.Project']"})
|
||||||
|
},
|
||||||
|
'projects.userstorystatus': {
|
||||||
|
'Meta': {'ordering': "['project', 'order', 'name']", 'object_name': 'UserStoryStatus', 'unique_together': "(('project', 'name'),)"},
|
||||||
|
'color': ('django.db.models.fields.CharField', [], {'max_length': '20', 'default': "'#999999'"}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'is_closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||||
|
'order': ('django.db.models.fields.IntegerField', [], {'default': '10'}),
|
||||||
|
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'us_statuses'", 'to': "orm['projects.Project']"}),
|
||||||
|
'wip_limit': ('django.db.models.fields.IntegerField', [], {'blank': 'True', 'null': 'True', 'default': 'None'})
|
||||||
|
},
|
||||||
|
'users.role': {
|
||||||
|
'Meta': {'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': ('djorm_pgarray.fields.TextArrayField', [], {'blank': 'True', 'null': 'True', 'default': '[]', 'dbtype': "'text'"}),
|
||||||
|
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'roles'", 'to': "orm['projects.Project']"}),
|
||||||
|
'slug': ('django.db.models.fields.SlugField', [], {'blank': 'True', 'max_length': '250'})
|
||||||
|
},
|
||||||
|
'users.user': {
|
||||||
|
'Meta': {'ordering': "['username']", 'object_name': 'User'},
|
||||||
|
'bio': ('django.db.models.fields.TextField', [], {'blank': 'True', 'default': "''"}),
|
||||||
|
'color': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '9', 'default': "'#31d025'"}),
|
||||||
|
'colorize_tags': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||||
|
'default_language': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '20', 'default': "''"}),
|
||||||
|
'default_timezone': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '20', 'default': "''"}),
|
||||||
|
'email': ('django.db.models.fields.EmailField', [], {'blank': 'True', 'max_length': '75'}),
|
||||||
|
'email_token': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '200', 'null': 'True', 'default': 'None'}),
|
||||||
|
'full_name': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '256'}),
|
||||||
|
'github_id': ('django.db.models.fields.IntegerField', [], {'blank': 'True', 'null': 'True'}),
|
||||||
|
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||||
|
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||||
|
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||||
|
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||||
|
'new_email': ('django.db.models.fields.EmailField', [], {'blank': 'True', 'max_length': '75', 'null': 'True'}),
|
||||||
|
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||||
|
'photo': ('django.db.models.fields.files.FileField', [], {'blank': 'True', 'max_length': '500', 'null': 'True'}),
|
||||||
|
'token': ('django.db.models.fields.CharField', [], {'blank': 'True', 'max_length': '200', 'null': 'True', 'default': 'None'}),
|
||||||
|
'username': ('django.db.models.fields.CharField', [], {'max_length': '30', 'unique': 'True'})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
complete_apps = ['projects']
|
|
@ -162,6 +162,8 @@ class Project(ProjectDefaults, TaggedMixin, models.Model):
|
||||||
is_private = models.BooleanField(default=False, null=False, blank=True,
|
is_private = models.BooleanField(default=False, null=False, blank=True,
|
||||||
verbose_name=_("is private"))
|
verbose_name=_("is private"))
|
||||||
|
|
||||||
|
tags_colors = TextArrayField(dimension=2, null=False, blank=True, verbose_name=_("tags colors"), default={})
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "project"
|
verbose_name = "project"
|
||||||
verbose_name_plural = "projects"
|
verbose_name_plural = "projects"
|
||||||
|
|
|
@ -18,7 +18,7 @@ from os import path
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from taiga.base.serializers import JsonField, PgArrayField, ModelSerializer
|
from taiga.base.serializers import JsonField, PgArrayField, ModelSerializer, TagsColorsField
|
||||||
from taiga.users.models import Role, User
|
from taiga.users.models import Role, User
|
||||||
from taiga.users.services import get_photo_or_gravatar_url
|
from taiga.users.services import get_photo_or_gravatar_url
|
||||||
from taiga.users.serializers import UserSerializer
|
from taiga.users.serializers import UserSerializer
|
||||||
|
@ -141,6 +141,7 @@ class ProjectSerializer(ModelSerializer):
|
||||||
stars = serializers.SerializerMethodField("get_stars_number")
|
stars = serializers.SerializerMethodField("get_stars_number")
|
||||||
my_permissions = serializers.SerializerMethodField("get_my_permissions")
|
my_permissions = serializers.SerializerMethodField("get_my_permissions")
|
||||||
i_am_owner = serializers.SerializerMethodField("get_i_am_owner")
|
i_am_owner = serializers.SerializerMethodField("get_i_am_owner")
|
||||||
|
tags_colors = TagsColorsField(required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Project
|
model = models.Project
|
||||||
|
|
|
@ -36,3 +36,5 @@ from .members import get_members_from_bulk
|
||||||
|
|
||||||
from .invitations import send_invitation
|
from .invitations import send_invitation
|
||||||
from .invitations import find_invited_user
|
from .invitations import find_invited_user
|
||||||
|
|
||||||
|
from .tags_colors import update_project_tags_colors_handler
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
# 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.conf import settings
|
||||||
|
|
||||||
|
from taiga.projects.services.filters import get_all_tags
|
||||||
|
|
||||||
|
from hashlib import sha1
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_color(tag):
|
||||||
|
color = sha1(tag.encode("utf-8")).hexdigest()[0:6]
|
||||||
|
return "#{}".format(color)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_new_color(tag, predefined_colors, exclude=[]):
|
||||||
|
colors = list(set(predefined_colors) - set(exclude))
|
||||||
|
if colors:
|
||||||
|
return colors[0]
|
||||||
|
return _generate_color(tag)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_unused_tags(project):
|
||||||
|
current_tags = get_all_tags(project)
|
||||||
|
project.tags_colors = list(filter(lambda x: x[0] in current_tags, project.tags_colors))
|
||||||
|
|
||||||
|
|
||||||
|
def update_project_tags_colors_handler(instance):
|
||||||
|
for tag in instance.tags:
|
||||||
|
defined_tags = map(lambda x: x[0], instance.project.tags_colors)
|
||||||
|
if tag not in defined_tags:
|
||||||
|
used_colors = map(lambda x: x[1], instance.project.tags_colors)
|
||||||
|
new_color = _get_new_color(tag, settings.TAGS_PREDEFINED_COLORS,
|
||||||
|
exclude=used_colors)
|
||||||
|
instance.project.tags_colors.append([tag, new_color])
|
||||||
|
|
||||||
|
remove_unused_tags(instance.project)
|
||||||
|
instance.project.save()
|
|
@ -29,6 +29,7 @@ from taiga.projects.userstories.models import UserStory
|
||||||
from taiga.projects.userstories import services as us_service
|
from taiga.projects.userstories import services as us_service
|
||||||
from taiga.projects.milestones.models import Milestone
|
from taiga.projects.milestones.models import Milestone
|
||||||
from taiga.projects.mixins.blocked import BlockedMixin
|
from taiga.projects.mixins.blocked import BlockedMixin
|
||||||
|
from taiga.projects.services.tags_colors import update_project_tags_colors_handler, remove_unused_tags
|
||||||
|
|
||||||
|
|
||||||
class Task(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, models.Model):
|
class Task(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, models.Model):
|
||||||
|
@ -139,3 +140,14 @@ def tasks_milestone_close_handler(sender, instance, **kwargs):
|
||||||
elif not instance.status.is_closed and instance.milestone.closed:
|
elif not instance.status.is_closed and instance.milestone.closed:
|
||||||
instance.milestone.closed = False
|
instance.milestone.closed = False
|
||||||
instance.milestone.save(update_fields=["closed"])
|
instance.milestone.save(update_fields=["closed"])
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(models.signals.post_save, sender=Task, dispatch_uid="task_update_project_colors")
|
||||||
|
def task_update_project_tags(sender, instance, **kwargs):
|
||||||
|
update_project_tags_colors_handler(instance)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(models.signals.post_delete, sender=Task, dispatch_uid="task_update_project_colors_on_delete")
|
||||||
|
def task_update_project_tags_on_delete(sender, instance, **kwargs):
|
||||||
|
remove_unused_tags(instance.project)
|
||||||
|
instance.project.save()
|
||||||
|
|
|
@ -25,6 +25,7 @@ from taiga.base.utils.slug import ref_uniquely
|
||||||
from taiga.projects.notifications import WatchedModelMixin
|
from taiga.projects.notifications import WatchedModelMixin
|
||||||
from taiga.projects.occ import OCCModelMixin
|
from taiga.projects.occ import OCCModelMixin
|
||||||
from taiga.projects.mixins.blocked import BlockedMixin
|
from taiga.projects.mixins.blocked import BlockedMixin
|
||||||
|
from taiga.projects.services.tags_colors import update_project_tags_colors_handler, remove_unused_tags
|
||||||
|
|
||||||
|
|
||||||
class RolePoints(models.Model):
|
class RolePoints(models.Model):
|
||||||
|
@ -165,3 +166,14 @@ def us_close_open_on_status_change(sender, instance, **kwargs):
|
||||||
service.close_userstory(instance)
|
service.close_userstory(instance)
|
||||||
else:
|
else:
|
||||||
service.open_userstory(instance)
|
service.open_userstory(instance)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(models.signals.post_save, sender=UserStory, dispatch_uid="user_story_update_project_colors")
|
||||||
|
def us_update_project_tags(sender, instance, **kwargs):
|
||||||
|
update_project_tags_colors_handler(instance)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(models.signals.post_delete, sender=UserStory, dispatch_uid="user_story_update_project_colors_on_delete")
|
||||||
|
def us_update_project_tags_on_delete(sender, instance, **kwargs):
|
||||||
|
remove_unused_tags(instance.project)
|
||||||
|
instance.project.save()
|
||||||
|
|
Loading…
Reference in New Issue