class SystemVariable(models.Model): """Model to store system wide variable like account/invoice counter Never use this model directly and use the trionyx.config.variables """ code = models.CharField(max_length=128, unique=True) value = models.JSONField() def __str__(self): """System variable representation""" return self.code
class Log(models.BaseModel): """Log""" CRITICAL = 50 ERROR = 40 WARNING = 30 INFO = 20 DEBUG = 10 NOTSET = 0 LEVEL_CHOICES = [ (CRITICAL, _('Critical')), (ERROR, _('Error')), (WARNING, _('Warning')), (INFO, _('Info')), (DEBUG, _('Debug')), (NOTSET, _('Not set')), ] log_hash = models.CharField(_('Log hash'), max_length=32) level = models.IntegerField(_('Level'), choices=LEVEL_CHOICES) message = models.TextField(_('Message')) file_path = models.CharField(_('File path'), max_length=256) file_line = models.IntegerField(_('File line')) traceback = models.TextField(_('Traceback'), default='') last_event = models.DateTimeField(_('Last event')) log_count = models.IntegerField(_('Log count'), default=1) objects = LogManager() # type: ignore class Meta: """Model meta description""" verbose_name = _('Log') verbose_name_plural = _('Logs')
class UserAttribute(models.Model): """User attribute to store system values for user""" user = models.ForeignKey(settings.AUTH_USER_MODEL, models.CASCADE, related_name='attributes') code = models.CharField(max_length=128, null=False) value = models.JSONField() objects = UserAttributeManager() class Meta: """Model meta description""" unique_together = ('user', 'code') def __str__(self): """User Attribute representation""" return self.code
class Item(models.BaseModel): TYPE_FEATURE = 10 TYPE_ENHANCEMENT = 20 TYPE_TASK = 30 TYPE_BUG = 40 TYPE_QUESTION = 50 TYPE_CHOICES = ( (TYPE_FEATURE, _('Feature')), (TYPE_ENHANCEMENT, _('Enhancement')), (TYPE_TASK, _('Task')), (TYPE_BUG, _('Bug')), (TYPE_QUESTION, _('Question')), ) PRIORITY_HIGHEST = 50 PRIORITY_HIGH = 40 PRIORITY_MEDIUM = 30 PRIORITY_LOW = 20 PRIORITY_LOWEST = 10 PRIORITY_CHOICES = ( (PRIORITY_HIGHEST, _('Highest')), (PRIORITY_HIGH, _('High')), (PRIORITY_MEDIUM, _('Medium')), (PRIORITY_LOW, _('Low')), (PRIORITY_LOWEST, _('Lowest')), ) project = models.ForeignKey(Project, related_name='items', on_delete=models.CASCADE) item_type = models.IntegerField(choices=TYPE_CHOICES, default=TYPE_FEATURE, blank=True) priority = models.IntegerField(choices=PRIORITY_CHOICES, default=PRIORITY_MEDIUM, blank=True) code = models.CharField(max_length=32) name = models.CharField(max_length=256) description = models.TextField(default='', null=True, blank=True) completed_on = models.DateField(default=None, null=True, blank=True) estimate = models.FloatField(null=True, blank=True) non_billable = models.BooleanField(default=False, blank=True) # Item stats total_worked = models.FloatField(default=0.0, blank=True) total_billed = models.FloatField(default=0.0, blank=True) @property def estimate_used(self): return 0.0 def save(self, *args, **kwargs): super().save(*args, **kwargs) if not self.code and self.project: with CacheLock('set-item-code', self.project_id): self.project.refresh_from_db() # get latest value self.project.item_increment_id += 1 self.code = f"{self.project.code}-{self.project.item_increment_id}" self.save(update_fields=['code']) self.project.save(update_fields=['item_increment_id']) @classmethod def get_type_icon(cls, item_type): icon_mapping = { cls.TYPE_FEATURE: 'fa fa-star', cls.TYPE_ENHANCEMENT: 'fa fa-bolt', cls.TYPE_TASK: 'fa fa-check', cls.TYPE_BUG: 'fa fa-bug', cls.TYPE_QUESTION: 'fa fa-question-circle', } badge_mapping = { cls.TYPE_FEATURE: 'badge-feature', cls.TYPE_ENHANCEMENT: 'badge-enhancement', cls.TYPE_TASK: 'badge-task', cls.TYPE_BUG: 'badge-bug', cls.TYPE_QUESTION: 'badge-question', } return f"<span class='badge badge-icon {badge_mapping[item_type]}'><i class='{icon_mapping[item_type]}'></i></span>" @classmethod def get_priority_icon(cls, priority): icon = 'fa fa-long-arrow-up' if priority <= cls.PRIORITY_LOW: icon = 'fa fa-long-arrow-down' class_mapping = { cls.PRIORITY_HIGHEST: 'priority-icon-highest', cls.PRIORITY_HIGH: 'priority-icon-high', cls.PRIORITY_MEDIUM: 'priority-icon-medium', cls.PRIORITY_LOW: 'priority-icon-low', cls.PRIORITY_LOWEST: 'priority-icon-lowest', } return f"<i class='{class_mapping[priority]} {icon}'></i>"
class Project(models.BaseModel): STATUS_DRAFT = 10 STATUS_ACTIVE = 20 STATUS_ON_HOLD = 30 STATUS_COMPLETED = 40 STATUS_CANCELED = 99 STATUS_CHOICES = [ (STATUS_DRAFT, _('Draft')), (STATUS_ACTIVE, _('Active')), (STATUS_ON_HOLD, _('On hold')), (STATUS_COMPLETED, _('Completed')), (STATUS_CANCELED, _('Canceled')), ] TYPE_FIXED = 10 TYPE_HOURLY_BASED = 20 TYPE_CHOICES = [ (TYPE_FIXED, _('Fixed')), (TYPE_HOURLY_BASED, _('Hourly based')), ] # Connect Project to other Model for_object_type = models.ForeignKey( 'contenttypes.ContentType', models.SET_NULL, blank=True, null=True, verbose_name=_('Object type'), ) for_object_id = models.BigIntegerField(_('Object ID'), blank=True, null=True) for_object = GenericForeignKey('for_object_type', 'for_object_id') name = models.CharField(max_length=256) code = models.CharField(max_length=10, unique=True) status = models.IntegerField(choices=STATUS_CHOICES, default=STATUS_DRAFT) project_type = models.IntegerField(choices=TYPE_CHOICES, default=TYPE_FIXED) description = models.TextField(default='', null=True, blank=True) deadline = models.DateField(default=None, null=True, blank=True) started_on = models.DateField(default=None, null=True, blank=True) completed_on = models.DateField(default=None, null=True, blank=True) fixed_price = models.PriceField(null=True, blank=True) project_hourly_rate = models.PriceField(null=True, blank=True) item_increment_id = models.IntegerField(default=0) # Project stats open_items = models.IntegerField(default=0) completed_items = models.IntegerField(default=0) total_items_estimate = models.FloatField(default=0.0) total_worked = models.FloatField(default=0.0) total_billed = models.FloatField(default=0.0) @property def hourly_rate(self): return float(self.project_hourly_rate if self. project_hourly_rate else app_settings.HOURLY_RATE) @property def estimate_used(self): return 0.0 def save(self, *args, **kwargs): self.code = str(self.code).upper() super().save(*args, **kwargs)
class User(models.BaseModel, AbstractBaseUser, PermissionsMixin): """User model""" email = models.EmailField(_('Email'), max_length=255, unique=True) first_name = models.CharField(_('First name'), max_length=64, blank=True, default='') last_name = models.CharField(_('Last name'), max_length=64, blank=True, default='') is_active = models.BooleanField(_('Active'), default=True) date_joined = models.DateTimeField(_('Date joined'), default=timezone.now) last_online = models.DateTimeField(_('Last online'), blank=True, null=True) avatar = models.ImageField(_('Avatar'), blank=True, upload_to='avatars/', default='') language = models.CharField(_('Language'), max_length=6, choices=settings.LANGUAGES, default=default_language) timezone = models.CharField(_('Timezone'), max_length=32, choices=TIMEZONES, default=default_timezone) USERNAME_FIELD = 'email' objects = UserManager() class Meta: """Model meta description""" verbose_name = _('User') verbose_name_plural = _('Users') def get_full_name(self): """Get full username if no name is set email is given""" if self.first_name and self.last_name: return "{} {}".format(self.first_name, self.last_name) return self.email def get_short_name(self): """Get short name if no name is set email is given""" if self.first_name: return self.first_name return self.email def set_attribute(self, code, value): """Set user attribute""" models.get_class(UserAttribute).objects.set_attribute( self, code, value) def get_attribute(self, code, default=None): """Get user attribute""" return models.get_class(UserAttribute).objects.get_attribute( self, code, default) @contextmanager def locale_override(self): """Override locale settings to user settings""" with translation.override(self.language), timezone.override( self.timezone): yield def send_email(self, subject, body='', html_template=None, template_context=None, files=None): """Send email to user""" if not body and not html_template: raise ValueError('You must supply a body or/and html_template') with self.locale_override(): message = EmailMultiAlternatives( subject=subject, body=body, from_email=settings.DEFAULT_FROM_EMAIL, to=[self.email]) if html_template: message.attach_alternative( render_to_string( html_template, template_context if template_context else {}), "text/html") if files: for file in files: message.attach(file.name, file.read()) return message.send()
class Task(models.BaseModel): """Task model""" SCHEDULED = 10 QUEUE = 20 LOCKED = 30 RUNNING = 40 COMPLETED = 50 FAILED = 99 STATUS_CHOICES = ( (SCHEDULED, _('Scheduled')), (QUEUE, _('Queue')), (LOCKED, _('Locked')), (RUNNING, _('Running')), (COMPLETED, _('Completed')), (FAILED, _('Failed')), ) celery_task_id = models.CharField(max_length=64, unique=True) status = models.IntegerField(_('Status'), choices=STATUS_CHOICES, default=QUEUE) identifier = models.CharField(_('Identifier'), max_length=255, default='not_set') description = models.TextField(_('Description'), default='') progress = models.PositiveIntegerField(_('Progress'), default=0) progress_output = models.JSONField(_('Progress output'), default=list) scheduled_at = models.DateTimeField(_('Scheduled at'), blank=True, null=True) started_at = models.DateTimeField(_('started at'), blank=True, null=True) execution_time = models.IntegerField(_('Execution time'), default=0) result = models.TextField(_('Result'), blank=True, default='') user = models.ForeignKey(settings.AUTH_USER_MODEL, models.SET_NULL, blank=True, null=True, verbose_name=_('Started by')) object_type = models.ForeignKey('contenttypes.ContentType', models.SET_NULL, blank=True, null=True, related_name='+', verbose_name=_('Object type')) object_id = models.BigIntegerField(_('Object ID'), blank=True, null=True) object = fields.GenericForeignKey('object_type', 'object_id') object_verbose_name = models.TextField(_('Object name'), blank=True, default='') class Meta: """Model meta description""" verbose_name = _('Task') verbose_name_plural = _('Tasks') def cancel_celery_task(self, kill=False): """ Make sure we cancel the task (if in queue/scheduled). :param: kill Also kill the task if it's running, defaults to False. """ celery_control = Control(current_app) celery_control.revoke(task_id=self.celery_task_id, terminate=kill)