patt/models.py

293 righe
10 KiB
Python

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 #%d: %s' % (self.id, 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):
return 'Task #%d: %s' % (self.id, 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'
@property
def style(self):
return {
COMMENTTYPE_APPRAISAL: 'taskappraisal',
}.get(self.type, 'taskcomment')
@property
def is_comment(self):
return self.type == COMMENTTYPE_COMMENT
@property
def is_appraisal(self):
return self.type == COMMENTTYPE_APPRAISAL
def sort_string(self):
return (self.creation_date, self.type, self.comment)
def __str__(self):
return 'Comment #%d: %s' % (self.id, self.task.name)
# 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)