• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Brunowar12 / TaskManagerSystem / 15166388190

21 May 2025 03:32PM UTC coverage: 92.47% (-0.3%) from 92.77%
15166388190

push

github

web-flow
Merge pull request #27 from Brunowar12/bugfix-and-pep

Bug fixes and code style cleanup

159 of 183 new or added lines in 24 files covered. (86.89%)

10 existing lines in 6 files now uncovered.

1621 of 1753 relevant lines covered (92.47%)

5.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.13
/projects/models.py
1
# mypy: disable-error-code=var-annotated
2

3
import uuid
6✔
4
from django.contrib.auth.models import Permission
6✔
5
from django.core.exceptions import ValidationError
6✔
6
from django.db import models
6✔
7
from django.conf import settings
6✔
8
from django.utils import timezone
6✔
9

10
from api.validators import TEXT_FIELD_VALIDATOR
6✔
11

12

13
class Project(models.Model):
6✔
14
    """Project with name, description, and owner"""
15
    name = models.CharField(max_length=128, validators=[TEXT_FIELD_VALIDATOR])
6✔
16
    description = models.TextField(blank=True)
6✔
17
    owner = models.ForeignKey(
6✔
18
        settings.AUTH_USER_MODEL,
19
        on_delete=models.CASCADE,
20
        related_name="projects",
21
    )
22
    created_at = models.DateTimeField(auto_now_add=True)
6✔
23

24
    class Meta:
6✔
25
        ordering = ["id"]
6✔
26

27
    def __str__(self):
6✔
28
        return f"{self.name} (Owner: {self.owner.username})"
×
29

30

31
class Role(models.Model):
6✔
32
    """Static project roles; prohibition to create custom ones"""
33
    name = models.CharField(max_length=64, unique=True)
6✔
34
    permissions = models.ManyToManyField(Permission, blank=True)
6✔
35
    
36
    def clean(self):
6✔
37
        fixed = settings.ROLE_ORDER
6✔
38
        if self.name not in fixed:
6✔
UNCOV
39
            raise ValidationError(
×
40
                f"Custom roles are not allowed. Use one of: {', '.join(fixed)}"
41
            )
42
            
43
    def save(self, *args, **kwargs):
6✔
44
        self.full_clean()
6✔
45
        super().save(*args, **kwargs)
6✔
46

47
    def __str__(self):
6✔
48
        return self.name
×
49

50

51
class ProjectMembership(models.Model):
6✔
52
    """The relationship 'user --> project --> role' """
53
    user = models.ForeignKey(
6✔
54
        settings.AUTH_USER_MODEL,
55
        on_delete=models.CASCADE,
56
        related_name="project_memberships",
57
    )
58
    project = models.ForeignKey(
6✔
59
        Project, on_delete=models.CASCADE, related_name="memberships"
60
    )
61
    role = models.ForeignKey(
6✔
62
        Role, on_delete=models.CASCADE, related_name="members"
63
    )
64

65
    class Meta:
6✔
66
        unique_together = ("user", "project")
6✔
67

68
    def __str__(self):
6✔
69
        return f"{self.user.username} - {self.project.name} ({self.role.name})"
×
70

71

72
class ProjectShareLink(models.Model):
6✔
73
    """Token for invitation to the project with a limits"""
74
    token = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
6✔
75
    project = models.ForeignKey(
6✔
76
        Project, on_delete=models.CASCADE, related_name="share_links"
77
    )
78
    role = models.ForeignKey(
6✔
79
        Role,
80
        on_delete=models.CASCADE,
81
        related_name="share_links",
82
        help_text="Role for invited user",
83
    )
84
    max_uses = models.PositiveIntegerField(
6✔
85
        null=True,
86
        blank=True,
87
        help_text="Maximum number of uses. Leave blank for unlimited uses",
88
    )
89
    used_count = models.PositiveIntegerField(default=0)
6✔
90
    is_active = models.BooleanField(default=True)
6✔
91
    expires_at = models.DateTimeField()
6✔
92
    created_by = models.ForeignKey(
6✔
93
        settings.AUTH_USER_MODEL, on_delete=models.CASCADE
94
    )
95
    created_at = models.DateTimeField(auto_now_add=True)
6✔
96

97
    def clean(self):
6✔
98
        errors = {}
×
99
        if self.max_uses is not None and self.max_uses <= 0:
×
NEW
100
            errors["max_uses"] = (
×
101
                "The max_uses value must be a positive number or left blank"
102
            )
103
        if self.max_uses is not None and self.used_count > self.max_uses:
×
104
            errors["used_count"] = "The number of uses exceeds the set limit"
×
105
        if self.expires_at <= timezone.now():
×
106
            errors["expires_at"] = "The expiration date must be in the future"
×
107

108
        if errors:
×
109
            raise ValidationError(errors)
×
110

111
    def is_expired(self):
6✔
112
        return timezone.now() >= self.expires_at
6✔
113

114
    def is_usage_exceeded(self):
6✔
115
        return self.max_uses is not None and self.used_count >= self.max_uses
6✔
116

117
    def is_valid(self):
6✔
118
        return (
6✔
119
            self.is_active
120
            and not self.is_expired()
121
            and not self.is_usage_exceeded()
122
        )
123

124
    def __str__(self):
6✔
125
        return f"Link to {self.project.name} ({self.role.name})"
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc