class WebhookLog(models.Model): webhook = models.ForeignKey(Webhook, null=False, blank=False, related_name="logs") url = models.URLField(null=False, blank=False, verbose_name=_("URL")) status = models.IntegerField(null=False, blank=False, verbose_name=_("status code")) request_data = JSONField(null=False, blank=False, verbose_name=_("request data")) request_headers = JSONField(null=False, blank=False, verbose_name=_("request headers"), default={}) response_data = models.TextField(null=False, blank=False, verbose_name=_("response data")) response_headers = JSONField(null=False, blank=False, verbose_name=_("response headers"), default={}) duration = models.FloatField(null=False, blank=False, verbose_name=_("duration"), default=0) created = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['-created', '-id']
class AbstractCustomAttribute(models.Model): name = models.CharField(null=False, blank=False, max_length=64, verbose_name=_("name")) description = models.TextField(null=False, blank=True, verbose_name=_("description")) type = models.CharField(null=False, blank=False, max_length=16, choices=choices.TYPES_CHOICES, default=choices.TEXT_TYPE, verbose_name=_("type")) order = models.BigIntegerField(null=False, blank=False, default=timestamp_ms, verbose_name=_("order")) project = models.ForeignKey("projects.Project", null=False, blank=False, related_name="%(class)ss", verbose_name=_("project")) extra = JSONField(blank=True, default=None, null=True) created_date = models.DateTimeField(null=False, blank=False, default=timezone.now, verbose_name=_("created date")) modified_date = models.DateTimeField(null=False, blank=False, verbose_name=_("modified date")) _importing = None class Meta: abstract = True ordering = ["project", "order", "name"] unique_together = ("project", "name") def __str__(self): return self.name def save(self, *args, **kwargs): if not self._importing or not self.modified_date: self.modified_date = timezone.now() return super().save(*args, **kwargs)
class AuthData(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="auth_data") key = models.SlugField(max_length=50) value = models.CharField(max_length=300) extra = JSONField() class Meta: unique_together = ["key", "value"]
class StorageEntry(models.Model): owner = models.ForeignKey(settings.AUTH_USER_MODEL, blank=False, null=False, related_name="storage_entries", verbose_name=_("owner")) 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")) key = models.CharField(max_length=255, null=False, blank=False, verbose_name=_("key")) value = JSONField(blank=True, default=None, null=True, verbose_name=_("value")) class Meta: verbose_name = "storage entry" verbose_name_plural = "storages entries" unique_together = ("owner", "key") ordering = ["owner", "key"]
class Timeline(models.Model): content_type = models.ForeignKey(ContentType, related_name="content_type_timelines") object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') namespace = models.CharField(max_length=250, default="default", db_index=True) event_type = models.CharField(max_length=250, db_index=True) project = models.ForeignKey(Project, null=True) data = JSONField() data_content_type = models.ForeignKey(ContentType, related_name="data_timelines") created = models.DateTimeField(default=timezone.now, db_index=True) class Meta: indexes = [ models.Index(fields=['namespace', '-created']), models.Index(fields=['content_type', 'object_id', '-created']), ]
class HistoryEntry(models.Model): """ Domain model that represents a history entry storage table. It is used for store object changes and comments. """ id = models.CharField(primary_key=True, max_length=255, unique=True, editable=False, default=_generate_uuid) project = models.ForeignKey("projects.Project") user = JSONField(null=True, blank=True, default=None) created_at = models.DateTimeField(default=timezone.now) type = models.SmallIntegerField(choices=HISTORY_TYPE_CHOICES) key = models.CharField(max_length=255, null=True, default=None, blank=True, db_index=True) # Stores the last diff diff = JSONField(null=True, blank=True, default=None) # Stores the values_diff cache values_diff_cache = JSONField(null=True, blank=True, default=None) # Stores the last complete frozen object snapshot snapshot = JSONField(null=True, blank=True, default=None) # Stores a values of all identifiers used in values = JSONField(null=True, blank=True, default=None) # Stores a comment comment = models.TextField(blank=True) comment_html = models.TextField(blank=True) delete_comment_date = models.DateTimeField(null=True, blank=True, default=None) delete_comment_user = JSONField(null=True, blank=True, default=None) # Historic version of comments comment_versions = JSONField(null=True, blank=True, default=None) edit_comment_date = models.DateTimeField(null=True, blank=True, default=None) # Flag for mark some history entries as # hidden. Hidden history entries are important # for save but not important to preview. # Order fields are the good example of this fields. is_hidden = models.BooleanField(default=False) # Flag for mark some history entries as complete # snapshot. The rest are partial snapshot. is_snapshot = models.BooleanField(default=False) _importing = None _owner = None _prefetched_owner = False @cached_property def is_change(self): return self.type == HistoryType.change @cached_property def is_create(self): return self.type == HistoryType.create @cached_property def is_delete(self): return self.type == HistoryType.delete @property def owner(self): if not self._prefetched_owner: pk = self.user["pk"] model = get_user_model() try: owner = model.objects.get(pk=pk) except model.DoesNotExist: owner = None self.prefetch_owner(owner) return self._owner def prefetch_owner(self, owner): self._owner = owner self._prefetched_owner = True def attach_user_info_to_comment_versions(self): if not self.comment_versions: return from tuesmon.users.serializers import UserSerializer user_ids = [ v["user"]["id"] for v in self.comment_versions if "user" in v and "id" in v["user"] ] users_by_id = { u.id: u for u in get_user_model().objects.filter(id__in=user_ids) } for version in self.comment_versions: user = users_by_id.get(version["user"]["id"], None) if user: version["user"] = UserSerializer(user).data @property def values_diff(self): if self.values_diff_cache is not None: return self.values_diff_cache result = {} users_keys = ["assigned_to", "owner"] def resolve_diff_value(key): value = None diff = get_diff_of_htmls(self.diff[key][0] or "", self.diff[key][1] or "") if diff: key = "{}_diff".format(key) value = (None, diff) return (key, value) def resolve_value(field, key): data = self.values[field] key = str(key) if key not in data: return None return data[key] for key in self.diff: value = None if key in IGNORE_DIFF_FIELDS: continue elif key in ["description", "content", "blocked_note"]: (key, value) = resolve_diff_value(key) elif key in users_keys: value = [resolve_value("users", x) for x in self.diff[key]] elif key == "assigned_users": diff_in, diff_out = self.diff[key] value_in = None value_out = None if diff_in: users_list = [ resolve_value("users", x) for x in diff_in if x ] value_in = ", ".join(filter(None, users_list)) if diff_out: users_list = [ resolve_value("users", x) for x in diff_out if x ] value_out = ", ".join(filter(None, users_list)) value = [value_in, value_out] elif key == "points": points = {} pointsold = self.diff["points"][0] pointsnew = self.diff["points"][1] # pointsold = pointsnew if pointsold is None: for role_id, point_id in pointsnew.items(): role_name = resolve_value("roles", role_id) points[role_name] = [ None, resolve_value("points", point_id) ] else: for role_id, point_id in pointsnew.items(): role_name = resolve_value("roles", role_id) oldpoint_id = pointsold.get(role_id, None) points[role_name] = [ resolve_value("points", oldpoint_id), resolve_value("points", point_id) ] # Process that removes points entries with # duplicate value. for role in dict(points): values = points[role] if values[1] == values[0]: del points[role] if points: value = points elif key == "attachments": attachments = { "new": [], "changed": [], "deleted": [], } oldattachs = {x["id"]: x for x in self.diff["attachments"][0]} newattachs = {x["id"]: x for x in self.diff["attachments"][1]} for aid in set( tuple(oldattachs.keys()) + tuple(newattachs.keys())): if aid in oldattachs and aid in newattachs: changes = make_diff_from_dicts( oldattachs[aid], newattachs[aid], excluded_keys=("filename", "url", "thumb_url")) if changes: change = { "filename": newattachs.get(aid, {}).get("filename", ""), "url": newattachs.get(aid, {}).get("url", ""), "thumb_url": newattachs.get(aid, {}).get("thumb_url", ""), "changes": changes } attachments["changed"].append(change) elif aid in oldattachs and aid not in newattachs: attachments["deleted"].append(oldattachs[aid]) elif aid not in oldattachs and aid in newattachs: attachments["new"].append(newattachs[aid]) if attachments["new"] or attachments["changed"] or attachments[ "deleted"]: value = attachments elif key == "custom_attributes": custom_attributes = { "new": [], "changed": [], "deleted": [], } oldcustattrs = { x["id"]: x for x in self.diff["custom_attributes"][0] or [] } newcustattrs = { x["id"]: x for x in self.diff["custom_attributes"][1] or [] } for aid in set( tuple(oldcustattrs.keys()) + tuple(newcustattrs.keys())): if aid in oldcustattrs and aid in newcustattrs: changes = make_diff_from_dicts(oldcustattrs[aid], newcustattrs[aid], excluded_keys=("name", "type")) newcustattr = newcustattrs.get(aid, {}) if changes: change_type = newcustattr.get("type", TEXT_TYPE) if change_type in [NUMBER_TYPE, CHECKBOX_TYPE]: old_value = oldcustattrs[aid].get("value") new_value = newcustattrs[aid].get("value") value_diff = [old_value, new_value] else: old_value = oldcustattrs[aid].get("value", "") new_value = newcustattrs[aid].get("value", "") value_diff = get_diff_of_htmls( old_value, new_value) change = { "name": newcustattr.get("name", ""), "changes": changes, "type": change_type, "value_diff": value_diff } custom_attributes["changed"].append(change) elif aid in oldcustattrs and aid not in newcustattrs: custom_attributes["deleted"].append(oldcustattrs[aid]) elif aid not in oldcustattrs and aid in newcustattrs: newcustattr = newcustattrs.get(aid, {}) change_type = newcustattr.get("type", TEXT_TYPE) if change_type in [NUMBER_TYPE, CHECKBOX_TYPE]: old_value = None new_value = newcustattrs[aid].get("value") value_diff = [old_value, new_value] else: new_value = newcustattrs[aid].get("value", "") value_diff = get_diff_of_htmls("", new_value) newcustattrs[aid]["value_diff"] = value_diff custom_attributes["new"].append(newcustattrs[aid]) if custom_attributes["new"] or custom_attributes[ "changed"] or custom_attributes["deleted"]: value = custom_attributes elif key == "user_stories": user_stories = { "new": [], "deleted": [], } olduss = {x["id"]: x for x in self.diff["user_stories"][0]} newuss = {x["id"]: x for x in self.diff["user_stories"][1]} for usid in set(tuple(olduss.keys()) + tuple(newuss.keys())): if usid in olduss and usid not in newuss: user_stories["deleted"].append(olduss[usid]) elif usid not in olduss and usid in newuss: user_stories["new"].append(newuss[usid]) if user_stories["new"] or user_stories["deleted"]: value = user_stories elif key in self.values: value = [resolve_value(key, x) for x in self.diff[key]] else: value = self.diff[key] if not value: continue result[key] = value self.values_diff_cache = result # Update values_diff_cache without dispatching signals HistoryEntry.objects.filter(pk=self.pk).update( values_diff_cache=self.values_diff_cache) return self.values_diff_cache class Meta: ordering = ["created_at"]
class WebNotification(models.Model): created = models.DateTimeField(default=timezone.now, db_index=True) read = models.DateTimeField(default=None, null=True) user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="web_notifications") event_type = models.PositiveIntegerField() data = JSONField()
class AbstractCustomAttributesValues(OCCModelMixin, models.Model): attributes_values = JSONField(null=False, blank=False, default={}, verbose_name=_("values")) class Meta: abstract = True ordering = ["id"]