Example #1
0
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']
Example #2
0
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"]
Example #3
0
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()
Example #4
0
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 AbstractCustomAttributesValues(OCCModelMixin, models.Model):
    attributes_values = JSONField(
        null=False, blank=False, default={}, verbose_name=_("values")
    )

    class Meta:
        abstract = True
        ordering = ["id"]
Example #6
0
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"),
        on_delete=models.CASCADE,
    )
    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)
Example #7
0
class CumulativeFlowDiagram(models.Model):
    project = models.ForeignKey("projects.Project",
                                null=False,
                                blank=False,
                                related_name="cumulative_flow_diagram_stats",
                                verbose_name=_("project"))
    data = JSONField(null=False, blank=False, verbose_name=_("data"))
    created_date = models.DateField(null=False,
                                    blank=False,
                                    verbose_name=_("created date"),
                                    default=datetime.date.today)

    class Meta:
        verbose_name = "cumulative flow diagram"
        verbose_name_plural = "cumulative flow diagrams"
        ordering = ["created_date", "project_id"]
Example #8
0
class BurnupChart(models.Model):
    project = models.ForeignKey("projects.Project",
                                null=False,
                                blank=False,
                                related_name="burnup_chart_stats",
                                verbose_name=_("project"))
    data = JSONField(null=False, blank=False, verbose_name=_("data"))
    created_date = models.DateField(null=False,
                                    blank=False,
                                    verbose_name=_("created date"),
                                    default=datetime.date.today)

    class Meta:
        verbose_name = "burnup chart"
        verbose_name_plural = "burnup charts"
        ordering = ["created_date", "project_id"]
Example #9
0
class Timeline(models.Model):
    content_type = models.ForeignKey(ContentType, related_name="content_type_timelines", on_delete=models.CASCADE)
    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, on_delete=models.CASCADE)
    data = JSONField()
    data_content_type = models.ForeignKey(ContentType, related_name="data_timelines", on_delete=models.CASCADE)
    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']),
        ]
Example #10
0
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"]),
        ]
Example #11
0
class VelocityChart(models.Model):
    project = models.ForeignKey("projects.Project",
                                null=False,
                                blank=False,
                                related_name="velocity_chart_stats",
                                verbose_name=_("project"))
    project_sprints_velocities = JSONField(
        null=False, blank=False, verbose_name=_("project sprints velocities"))
    created_date = models.DateField(null=False,
                                    blank=False,
                                    verbose_name=_("created date"),
                                    default=datetime.date.today)

    class Meta:
        verbose_name = "velocity chart"
        verbose_name_plural = "velocity charts"
        ordering = ["created_date", "project_id"]
Example #12
0
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:
        index_together = [
            ('content_type', 'object_id', 'namespace'),
        ]
Example #13
0
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 taiga.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 == "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"))

                        newcustattr = newcustattrs.get(aid, {})
                        if changes:
                            change_type = newcustattr.get("type", TEXT_TYPE)
                            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:
                        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"]
Example #14
0
class User(AbstractBaseUser, PermissionsMixin):
    uuid = models.CharField(max_length=32, editable=False, null=False,
                            blank=False, unique=True, default=get_default_uuid)
    username = models.CharField(_("username"), max_length=255, unique=True,
        help_text=_("Required. 30 characters or fewer. Letters, numbers and "
                    "/./-/_ characters"),
        validators=[
            validators.RegexValidator(re.compile("^[\w.-]+$"), _("Enter a valid username."), "invalid")
        ])
    email = models.EmailField(_("email address"), max_length=255, blank=True, unique=True)
    is_active = models.BooleanField(_("active"), default=True,
        help_text=_("Designates whether this user should be treated as "
                    "active. Unselect this instead of deleting accounts."))

    full_name = models.CharField(_("full name"), max_length=256, blank=True)
    color = models.CharField(max_length=9, null=False, blank=True, default=generate_random_hex_color,
                             verbose_name=_("color"))
    bio = models.TextField(null=False, blank=True, default="", verbose_name=_("biography"))
    photo = models.FileField(upload_to=get_user_file_path,
                             max_length=500, null=True, blank=True,
                             verbose_name=_("photo"))
    date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
    accepted_terms = models.BooleanField(_("accepted terms"), default=True)
    read_new_terms = models.BooleanField(_("new terms read"), default=False)
    lang = models.CharField(max_length=20, null=True, blank=True, default="",
                            verbose_name=_("default language"))
    theme = models.CharField(max_length=100, null=True, blank=True, default="",
                            verbose_name=_("default theme"))
    timezone = models.CharField(max_length=20, null=True, blank=True, default="",
                                verbose_name=_("default timezone"))
    colorize_tags = models.BooleanField(null=False, blank=True, default=False,
                                        verbose_name=_("colorize tags"))
    token = models.CharField(max_length=200, null=True, blank=True, default=None,
                             verbose_name=_("token"))

    email_token = models.CharField(max_length=200, null=True, blank=True, default=None,
                         verbose_name=_("email token"))

    new_email = models.EmailField(_("new email address"), null=True, blank=True)

    is_system = models.BooleanField(null=False, blank=False, default=False)


    max_private_projects = models.IntegerField(null=True, blank=True,
                                               default=settings.MAX_PRIVATE_PROJECTS_PER_USER,
                                               verbose_name=_("max number of owned private projects"))
    max_public_projects = models.IntegerField(null=True, blank=True,
                                              default=settings.MAX_PUBLIC_PROJECTS_PER_USER,
                                              verbose_name=_("max number of owned public projects"))
    max_memberships_private_projects = models.IntegerField(null=True, blank=True,
                                                           default=settings.MAX_MEMBERSHIPS_PRIVATE_PROJECTS,
                                                           verbose_name=_("max number of memberships for "
                                                                          "each owned private project"))
    max_memberships_public_projects = models.IntegerField(null=True, blank=True,
                                                          default=settings.MAX_MEMBERSHIPS_PUBLIC_PROJECTS,
                                                          verbose_name=_("max number of memberships for "
                                                                         "each owned public project"))
    projects_activity = JSONField(null=True, blank=True, default=None)

    _cached_memberships = None
    _cached_liked_ids = None
    _cached_watched_ids = None
    _cached_notify_levels = None

    USERNAME_FIELD = "username"
    REQUIRED_FIELDS = ["email"]

    objects = UserManager()

    class Meta:
        verbose_name = "user"
        verbose_name_plural = "users"
        ordering = ["username"]

    def __str__(self):
        return self.get_full_name()

    def _fill_cached_memberships(self):
        self._cached_memberships = {}
        qs = self.memberships.select_related("user", "project", "role")
        for membership in qs.all():
            self._cached_memberships[membership.project.id] = membership

    @property
    def cached_memberships(self):
        if self._cached_memberships is None:
            self._fill_cached_memberships()

        return self._cached_memberships.values()

    def cached_membership_for_project(self, project):
        if self._cached_memberships is None:
            self._fill_cached_memberships()

        return self._cached_memberships.get(project.id, None)

    def is_fan(self, obj):
        if self._cached_liked_ids is None:
            self._cached_liked_ids = set()
            for like in self.likes.select_related("content_type").all():
                like_id = "{}-{}".format(like.content_type.id, like.object_id)
                self._cached_liked_ids.add(like_id)

        obj_type = ContentType.objects.get_for_model(obj)
        obj_id = "{}-{}".format(obj_type.id, obj.id)
        return obj_id in self._cached_liked_ids

    def is_watcher(self, obj):
        if self._cached_watched_ids is None:
            self._cached_watched_ids = set()
            for watched in self.watched.select_related("content_type").all():
                watched_id = "{}-{}".format(watched.content_type.id, watched.object_id)
                self._cached_watched_ids.add(watched_id)

            notify_policies = self.notify_policies.select_related("project")\
                .exclude(notify_level=NotifyLevel.none)

            for notify_policy in notify_policies:
                obj_type = ContentType.objects.get_for_model(notify_policy.project)
                watched_id = "{}-{}".format(obj_type.id, notify_policy.project.id)
                self._cached_watched_ids.add(watched_id)

        obj_type = ContentType.objects.get_for_model(obj)
        obj_id = "{}-{}".format(obj_type.id, obj.id)
        return obj_id in self._cached_watched_ids

    def get_notify_level(self, project):
        if self._cached_notify_levels is None:
            self._cached_notify_levels = {}
            for notify_policy in self.notify_policies.select_related("project"):
                self._cached_notify_levels[notify_policy.project.id] = notify_policy.notify_level

        return self._cached_notify_levels.get(project.id, None)

    def get_short_name(self):
        "Returns the short name for the user."
        return self.username

    def get_full_name(self):
        return self.full_name or self.username or self.email

    def contacts_visible_by_user(self, user):
        qs = User.objects.filter(is_active=True)
        project_ids = services.get_visible_project_ids(self, user)
        qs = qs.filter(memberships__project_id__in=project_ids)
        qs = qs.exclude(id=self.id)
        return qs

    def save(self, *args, **kwargs):
        get_token_for_user(self, "cancel_account")
        super().save(*args, **kwargs)

    def cancel(self):
        with advisory_lock("delete-user"):
            deleted_user_prefix = "deleted-user-{}".format(timestamp_ms())
            self.username = slugify_uniquely(deleted_user_prefix, User, slugfield="username")
            self.email = "{}@taiga.io".format(self.username)
            self.is_active = False
            self.full_name = "Deleted user"
            self.color = ""
            self.bio = ""
            self.lang = ""
            self.theme = ""
            self.timezone = ""
            self.colorize_tags = True
            self.token = None
            self.set_unusable_password()
            self.photo = None
            self.save()
        self.auth_data.all().delete()

        # Blocking all owned projects
        self.owned_projects.update(blocked_code=BLOCKED_BY_OWNER_LEAVING)

        # Remove all memberships
        self.memberships.all().delete()
Example #15
0
class UserStory(OCCModelMixin, WatchedModelMixin, BlockedMixin, TaggedMixin, models.Model):
    ref = models.BigIntegerField(db_index=True, null=True, blank=True, default=None,
                                 verbose_name=_("ref"))
    milestone = models.ForeignKey("milestones.Milestone", null=True, blank=True,
                                  default=None, related_name="user_stories",
                                  on_delete=models.SET_NULL, verbose_name=_("milestone"))
    project = models.ForeignKey("projects.Project", null=False, blank=False,
                                related_name="user_stories", verbose_name=_("project"))
    owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True,
                              related_name="owned_user_stories", verbose_name=_("owner"),
                              on_delete=models.SET_NULL)
    status = models.ForeignKey("projects.UserStoryStatus", null=True, blank=True,
                               related_name="user_stories", verbose_name=_("status"),
                               on_delete=models.SET_NULL)
    is_closed = models.BooleanField(default=False)
    points = models.ManyToManyField("projects.Points", blank=False,
                                    related_name="userstories", through="RolePoints",
                                    verbose_name=_("points"))

    backlog_order = models.BigIntegerField(null=False, blank=False, default=timestamp_ms,
                                        verbose_name=_("backlog order"))
    sprint_order = models.BigIntegerField(null=False, blank=False, default=timestamp_ms,
                                       verbose_name=_("sprint order"))
    kanban_order = models.BigIntegerField(null=False, blank=False, default=timestamp_ms,
                                       verbose_name=_("kanban order"))

    created_date = models.DateTimeField(null=False, blank=False,
                                        verbose_name=_("created date"),
                                        default=timezone.now)
    modified_date = models.DateTimeField(null=False, blank=False,
                                         verbose_name=_("modified date"))
    finish_date = models.DateTimeField(null=True, blank=True,
                                       verbose_name=_("finish date"))
    subject = models.TextField(null=False, blank=False,
                               verbose_name=_("subject"))
    description = models.TextField(null=False, blank=True, verbose_name=_("description"))
    assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True,
                                    default=None, related_name="userstories_assigned_to_me",
                                    verbose_name=_("assigned to"))
    client_requirement = models.BooleanField(default=False, null=False, blank=True,
                                             verbose_name=_("is client requirement"))
    team_requirement = models.BooleanField(default=False, null=False, blank=True,
                                           verbose_name=_("is team requirement"))
    attachments = GenericRelation("attachments.Attachment")
    generated_from_issue = models.ForeignKey("issues.Issue", null=True, blank=True,
                                             on_delete=models.SET_NULL,
                                             related_name="generated_user_stories",
                                             verbose_name=_("generated from issue"))
    external_reference = ArrayField(models.TextField(null=False, blank=False),
                                    null=True, blank=True, default=None, verbose_name=_("external reference"))

    tribe_gig = PickledObjectField(null=True, blank=True, default=None,
                                   verbose_name="taiga tribe gig")
    publish_date = models.TextField(null=True, blank=True, verbose_name=_("publish date"))
    publish_time = models.TextField(null=True, blank=True, verbose_name=_("publish time"))
    social_media_attributes = JSONField(null=True, blank=True, verbose_name=_("social media attributes"))

    _importing = None

    class Meta:
        verbose_name = "user story"
        verbose_name_plural = "user stories"
        ordering = ["project", "backlog_order", "ref"]

    def save(self, *args, **kwargs):
        if not self._importing or not self.modified_date:
            self.modified_date = timezone.now()

        if not self.status:
            self.status = self.project.default_us_status

        super().save(*args, **kwargs)

        if not self.role_points.all():
            for role in self.project.roles.all():
                RolePoints.objects.create(role=role,
                                          points=self.project.default_points,
                                          user_story=self)

    def __str__(self):
        return "({1}) {0}".format(self.ref, self.subject)

    def __repr__(self):
        return "<UserStory %s>" % (self.id)

    def get_role_points(self):
        return self.role_points

    def get_total_points(self):
        not_null_role_points = [
            rp.points.value
            for rp in self.role_points.all()
            if rp.points.value is not None
       ]

        #If we only have None values the sum should be None
        if not not_null_role_points:
            return None

        return sum(not_null_role_points)