123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- import datetime
- from django.contrib.auth.models import User
- from django.db import models
- from django.utils.dateformat import format
- from django.utils.translation import gettext as _
- from simple_history.models import HistoricalRecords
-
- # PROJECTSTATE (REQ-??)
- #
- PROJECTSTATE_OPEN = 0
- PROJECTSTATE_CLOSED = 2
- PROJECTS_IN_WORK = (PROJECTSTATE_OPEN, )
- #
- PROJECTSTATE_CHOICES = (
- (PROJECTSTATE_OPEN, _('Open')),
- (PROJECTSTATE_CLOSED, _('Closed')),
- )
-
-
- # TASKSTATE (REQ-14)
- #
- TASKSTATE_OPEN = 0
- TASKSTATE_FINISHED = 1
- TASKSTATE_CLOSED = 2
- TASKSTATE_CANCELED = 3
- TASKS_IN_WORK = (TASKSTATE_OPEN, TASKSTATE_FINISHED, )
- #
- TASKSTATE_CHOICES = (
- (TASKSTATE_OPEN, _('Open')),
- (TASKSTATE_FINISHED, _('Finished')),
- (TASKSTATE_CLOSED, _('Closed')),
- (TASKSTATE_CANCELED, _('Cancelled')),
- )
-
-
- # TASKPRIORITY (REQ-??)
- #
- PRIO_CHOICES = (
- (1, _('1 - Highest')),
- (2, _('2 - High')),
- (3, _('3 - Above-Average')),
- (4, _('4 - Average')),
- (5, _('5 - Below-Average')),
- (6, _('6 - Low')),
- (7, _('7 - Lowest')),
- )
-
-
- # TASKPROGRESS (REQ-??)
- #
- PRORGRESS_CHOICES = (
- (0, '0 %'),
- (10, '10 %'),
- (20, '20 %'),
- (30, '30 %'),
- (40, '40 %'),
- (50, '50 %'),
- (60, '60 %'),
- (70, '70 %'),
- (80, '80 %'),
- (90, '90 %'),
- (100, '100 %'),
- )
-
- # COMMENTTYPE (REQ-??)
- #
- COMMENTTYPE_COMMENT = 0
- COMMENTTYPE_APPRAISAL = 1
- #
- COMMENTTYPE_CHOICES = (
- (COMMENTTYPE_APPRAISAL, _('Appraisal')),
- (COMMENTTYPE_COMMENT, _('Comment')),
- )
-
-
- # GENERAL Methods and Classes
- #
- class ModelList(list):
- def __init__(self, lst, chk=None, user=None):
- if chk is None:
- list.__init__(self, lst)
- else:
- list.__init__(self)
- for e in lst:
- acc = chk(e, user)
- if acc.read:
- self.append(e)
-
- def sort(self, **kwargs):
- list.sort(self, key=lambda entry: entry.sort_string(), reverse=kwargs.get('reverse', False))
-
-
- # PROCECT Model
- #
- class Project(models.Model):
- name = models.CharField(max_length=128) # REQ-7
- description = models.TextField(default='', blank=True) # REQ-8
- state = models.IntegerField(choices=PROJECTSTATE_CHOICES, default=PROJECTSTATE_OPEN) # REQ-26
- role_leader = models.ManyToManyField(User, related_name='role_leader') # REQ-9
- role_member = models.ManyToManyField(User, related_name='role_member', blank=True) # REQ-12
- role_visitor = models.ManyToManyField(User, related_name='role_visitor', blank=True) # REQ-33
- creation_date = models.DateTimeField(auto_now_add=True)
- days_late = models.IntegerField(default=0, choices=((-1, _('Off')), (0, 0), ))
- days_very_soon = models.IntegerField(default=3, choices=((-1, _('Off')), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (7, 7), (9, 9), ))
- days_soon = models.IntegerField(default=7, choices=((-1, _('Off')), (1, 1), (2, 2), (3, 3), (5, 5), (7, 7), (10, 10), (14, 14), ))
-
- @property
- def attachment_target_path(self):
- if self.id:
- return 'patt/project/%d' % self.id
- else:
- return 'patt/project/new'
-
- @property
- def formatted_leaders(self):
- if self.id: # in preview (before saving), the ManyToManyFields are not available
- return ', '.join([user.username for user in self.role_leader.all()]) or '-'
- else:
- return '...'
-
- @property
- def formatted_members(self):
- if self.id: # in preview (before saving), the ManyToManyFields are not available
- return ', '.join([user.username for user in self.role_member.all()]) or '-'
- else:
- return '...'
-
- @property
- def formatted_visitors(self):
- if self.id: # in preview (before saving), the ManyToManyFields are not available
- return ', '.join([user.username for user in self.role_visitor.all()]) or '-'
- else:
- return '...'
-
- def sort_string(self):
- return (self.state, self.name)
-
- def __str__(self):
- return 'Project: %s' % self.name
-
-
- # TASK Model
- #
- class Task(models.Model):
- FUSION_STATE_FINISHED = 0
- FUSION_STATE_NORMAL = 1
- FUSION_STATE_SOON = 2
- FUSION_STATE_VERY_SOON = 3
- FUSION_STATE_LATE = 4
- #
- SAVE_ON_CHANGE_FIELDS = ['state', 'priority', 'targetdate', 'progress', 'name', 'description', 'assigned_user', 'project']
- #
- state = models.IntegerField(default=TASKSTATE_OPEN) # REQ-14
- priority = models.IntegerField(choices=PRIO_CHOICES, default=4) # REQ-15
- targetdate = models.DateField(null=True, blank=True) # REQ-16
- progress = models.IntegerField(default=0, choices=PRORGRESS_CHOICES) # REQ-17
- name = models.CharField(max_length=150, default='') # REQ-?? (18)
- description = models.TextField(default='', blank=True) # REQ-??
- assigned_user = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL) # REQ-31
- project = models.ForeignKey(Project, on_delete=models.CASCADE) # REQ-??
- creation_date = models.DateTimeField(auto_now_add=True)
- #
- history = HistoricalRecords()
-
- @property
- def taskname_prefix(self):
- if self.assigned_user:
- return '**#%d //(%s)//:** ' % (self.id, self.assigned_user.username)
- else:
- return '**#%d:** ' % self.id
-
- def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
- if self.id and not force_update:
- orig = Task.objects.get(id=self.id)
- for key in self.SAVE_ON_CHANGE_FIELDS:
- if getattr(self, key) != getattr(orig, key):
- break
- else:
- self.save_needed = False
- return False
- self.save_needed = True
- return models.Model.save(self, force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
-
- @property
- def attachment_target_path(self):
- if self.id:
- return 'patt/task/%d' % self.id
- else:
- return 'patt/task/new'
-
- @property
- def comments(self):
- rv = ModelList(self.comment_set.all())
- rv.sort(reverse=True)
- return rv
-
- def datafusion_state(self):
- if self.state in [TASKSTATE_CANCELED, TASKSTATE_CLOSED, TASKSTATE_FINISHED]:
- return self.FUSION_STATE_FINISHED
- else:
- if self.targetdate is not None:
- if type(self.targetdate) == datetime.date:
- targetdate = self.targetdate
- else:
- targetdate = datetime.datetime.strptime(self.targetdate, '%Y-%m-%d').date()
- if targetdate - datetime.date.today() <= datetime.timedelta(self.project.days_late) and self.project.days_late >= 0:
- return self.FUSION_STATE_LATE
- elif targetdate - datetime.date.today() <= datetime.timedelta(self.project.days_very_soon) and self.project.days_very_soon >= 0:
- return self.FUSION_STATE_VERY_SOON
- elif targetdate - datetime.date.today() <= datetime.timedelta(self.project.days_soon) and self.project.days_soon >= 0:
- return self.FUSION_STATE_SOON
- return self.FUSION_STATE_NORMAL
-
- @property
- def class_by_state(self):
- return {
- self.FUSION_STATE_FINISHED: 'task-finished',
- self.FUSION_STATE_NORMAL: 'task-normal',
- self.FUSION_STATE_VERY_SOON: 'task-very-soon',
- self.FUSION_STATE_SOON: 'task-soon',
- self.FUSION_STATE_LATE: 'task-late',
- }.get(self.datafusion_state())
-
- def sort_string(self):
- if self.targetdate:
- td = int(format(self.targetdate, 'U'))
- else:
- td = 999999999999
- return (100 - self.datafusion_state(), self.state, self.priority, td, self.progress, self.name)
-
- def __str__(self):
- if self.project:
- return 'Task: %s - %s' % (self.project.name, self.name)
- else:
- return 'Task: %s' % (self.name)
-
-
- # COMMENT Model
- #
- class Comment(models.Model): # REQ-19 and REQ-20
- SAVE_ON_CHANGE_FIELDS = ['task', 'user', 'type', 'comment']
- task = models.ForeignKey(Task, on_delete=models.CASCADE)
- user = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL)
- type = models.IntegerField(choices=COMMENTTYPE_CHOICES, default=COMMENTTYPE_COMMENT)
- creation_date = models.DateTimeField(auto_now_add=True)
- comment = models.TextField()
- #
- history = HistoricalRecords()
-
- def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
- if self.id and not force_update:
- orig = Comment.objects.get(id=self.id)
- for key in self.SAVE_ON_CHANGE_FIELDS:
- if getattr(self, key) != getattr(orig, key):
- break
- else:
- self.save_needed = False
- return False
- self.save_needed = True
- return models.Model.save(self, force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
-
- @property
- def attachment_target_path(self):
- if self.id:
- return 'patt/comment/%d' % self.id
- else:
- return 'patt/comment/new'
-
- def sort_string(self):
- return (self.creation_date, self.type, self.comment)
-
- def __str__(self):
- return 'Comment: %s - %d' % (self.task.name, self.id)
-
-
- # SEARCH Model
- #
- class Search(models.Model):
- name = models.CharField(max_length=48)
- search_txt = models.TextField(default='', blank=True)
- user = models.ForeignKey(User, on_delete=models.CASCADE)
|