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

akvo / akvo-mis / #428

28 Oct 2025 07:41AM UTC coverage: 88.63% (-0.02%) from 88.647%
#428

push

coveralls-python

ifirmawan
[#122] RWS: Update labels for non-operational issues in monitoring form

3505 of 4069 branches covered (86.14%)

Branch coverage included in aggregate %.

7237 of 8051 relevant lines covered (89.89%)

0.9 hits per line

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

98.4
backend/api/v1/v1_profile/models.py
1
from django.db import models
1✔
2
from django.contrib.postgres.fields import ArrayField
1✔
3
from django.db.models.signals import pre_save
1✔
4
from django.dispatch import receiver
1✔
5
from api.v1.v1_profile.constants import (
1✔
6
    DataAccessTypes,
7
    FeatureAccessTypes,
8
    FeatureTypes,
9
)
10
from api.v1.v1_users.models import SystemUser
1✔
11

12

13
class Levels(models.Model):
1✔
14
    name = models.CharField(max_length=50)
1✔
15
    level = models.IntegerField()
1✔
16

17
    def __str__(self):
1✔
18
        return self.name
×
19

20
    class Meta:
1✔
21
        db_table = "levels"
1✔
22

23

24
class Administration(models.Model):
1✔
25
    parent = models.ForeignKey(
1✔
26
        "self",
27
        on_delete=models.PROTECT,
28
        # NOTE: should be named 'children'
29
        related_name="parent_administration",
30
        default=None,
31
        null=True,
32
    )
33
    code = models.CharField(max_length=255, null=True, default=None)
1✔
34
    level = models.ForeignKey(
1✔
35
        to=Levels, on_delete=models.CASCADE, related_name="administrator_level"
36
    )
37
    name = models.TextField()
1✔
38
    path = models.TextField(null=True, default=None)
1✔
39

40
    def __str__(self):
1✔
41
        return self.name
1✔
42

43
    @property
1✔
44
    def ancestors(self):
1✔
45
        if self.path:
1✔
46
            ids = self.path.split(".")[:-1]
1✔
47
            administrations = Administration.objects.filter(
1✔
48
                id__in=ids
49
            ).order_by("level__level")
50
            return administrations
1✔
51
        return None
1✔
52

53
    @property
1✔
54
    def full_name(self):
1✔
55
        if self.path:
1✔
56
            names = " - ".join([a.name for a in self.ancestors])
1✔
57
            return "{} - {}".format(names, self.name)
1✔
58
        return self.name
1✔
59

60
    @property
1✔
61
    def full_path_name(self):
1✔
62
        if self.path:
1✔
63
            names = "|".join([a.name for a in self.ancestors])
1✔
64
            return "{}|{}".format(names, self.name)
1✔
65
        return self.name
1✔
66

67
    @property
1✔
68
    def administration_column(self):
1✔
69
        if self.path:
1✔
70
            names = "|".join([a.name for a in self.ancestors])
1✔
71
            return "{}|{}".format(names, self.name)
1✔
72
        return self.name
1✔
73

74
    class Meta:
1✔
75
        db_table = "administrator"
1✔
76

77

78
@receiver(pre_save, sender=Administration)
1✔
79
def set_administration_path(sender, instance: Administration, **_):
1✔
80
    if not instance.parent:
1✔
81
        return
1✔
82
    if instance.path:
1✔
83
        return
1✔
84
    parent = instance.parent
1✔
85
    instance.path = f"{parent.path or ''}{parent.id}."
1✔
86

87

88
class AdministrationAttribute(models.Model):
1✔
89
    class Type(models.TextChoices):
1✔
90
        VALUE = "value", "Value"
1✔
91
        OPTION = "option", "Option"
1✔
92
        MULTIPLE_OPTION = "multiple_option", "Multiple option"
1✔
93
        AGGREGATE = "aggregate", "Aggregate"
1✔
94

95
    name = models.TextField()
1✔
96
    type = models.CharField(
1✔
97
        max_length=25, choices=Type.choices, default=Type.VALUE
98
    )
99
    options = ArrayField(
1✔
100
        models.CharField(max_length=255, null=True), default=list, blank=True
101
    )
102

103
    class Meta:
1✔
104
        db_table = "administration_attribute"
1✔
105

106

107
class AdministrationAttributeValue(models.Model):
1✔
108
    administration = models.ForeignKey(
1✔
109
        to=Administration, on_delete=models.CASCADE, related_name="attributes"
110
    )
111
    attribute = models.ForeignKey(
1✔
112
        to=AdministrationAttribute, on_delete=models.CASCADE
113
    )
114
    value = models.JSONField(default=dict)
1✔
115

116
    class Meta:
1✔
117
        db_table = "administration_attribute_value"
1✔
118

119

120
class Entity(models.Model):
1✔
121
    name = models.TextField()
1✔
122

123
    class Meta:
1✔
124
        db_table = "entities"
1✔
125

126

127
class EntityData(models.Model):
1✔
128
    name = models.TextField()
1✔
129
    code = models.CharField(max_length=255, null=True, default=None)
1✔
130
    entity = models.ForeignKey(
1✔
131
        to=Entity, on_delete=models.PROTECT, related_name="entity_data"
132
    )
133
    administration = models.ForeignKey(
1✔
134
        to=Administration, on_delete=models.PROTECT, related_name="entity_data"
135
    )
136

137
    class Meta:
1✔
138
        db_table = "entity_data"
1✔
139

140

141
# New code for roles and access management
142

143

144
class Role(models.Model):
1✔
145
    name = models.CharField(max_length=100, unique=True)
1✔
146
    description = models.TextField(null=True, blank=True)
1✔
147
    administration_level = models.ForeignKey(
1✔
148
        to=Levels,
149
        on_delete=models.CASCADE,
150
        related_name="role_administration_level",
151
    )
152

153
    def __str__(self):
1✔
154
        return self.name
1✔
155

156
    class Meta:
1✔
157
        db_table = "role"
1✔
158

159

160
class RoleAccess(models.Model):
1✔
161
    role = models.ForeignKey(
1✔
162
        to=Role, on_delete=models.CASCADE, related_name="role_role_access"
163
    )
164
    data_access = models.IntegerField(
1✔
165
        choices=DataAccessTypes.FieldStr.items(),
166
        default=DataAccessTypes.read,
167
    )
168

169
    def __str__(self):
1✔
170
        return (
1✔
171
            f"{self.role.name} - {DataAccessTypes.FieldStr[self.data_access]}"
172
        )
173

174
    class Meta:
1✔
175
        unique_together = ("role", "data_access")
1✔
176
        db_table = "role_access"
1✔
177

178

179
class RoleFeatureAccess(models.Model):
1✔
180
    role = models.ForeignKey(
1✔
181
        to=Role, on_delete=models.CASCADE,
182
        related_name="role_role_feature_access"
183
    )
184
    type = models.IntegerField(
1✔
185
        choices=FeatureTypes.FieldStr.items(),
186
    )
187
    access = models.IntegerField(
1✔
188
        choices=FeatureAccessTypes.FieldStr.items(),
189
    )
190

191
    def __str__(self):
1✔
192
        return (
×
193
            f"{self.role.name} - {FeatureTypes.FieldStr[self.type]} - "
194
            f"{FeatureAccessTypes.FieldStr[self.access]}"
195
        )
196

197
    class Meta:
1✔
198
        unique_together = ("role", "type", "access")
1✔
199
        db_table = "role_feature_access"
1✔
200

201

202
class UserRole(models.Model):
1✔
203
    user = models.ForeignKey(
1✔
204
        to=SystemUser,
205
        on_delete=models.CASCADE,
206
        related_name="user_user_role"
207
    )
208
    role = models.ForeignKey(
1✔
209
        to=Role, on_delete=models.CASCADE, related_name="role_user_role"
210
    )
211
    administration = models.ForeignKey(
1✔
212
        to=Administration,
213
        on_delete=models.CASCADE,
214
        related_name="user_role_administration",
215
    )
216

217
    def is_approver(self):
1✔
218
        return self.role.role_role_access.filter(
1✔
219
            data_access=DataAccessTypes.approve
220
        ).exists()
221

222
    def is_submitter(self):
1✔
223
        return self.role.role_role_access.filter(
1✔
224
            data_access=DataAccessTypes.submit
225
        ).exists()
226

227
    def is_editor(self):
1✔
228
        return self.role.role_role_access.filter(
1✔
229
            data_access=DataAccessTypes.edit
230
        ).exists()
231

232
    def can_delete(self):
1✔
233
        return self.role.role_role_access.filter(
1✔
234
            data_access=DataAccessTypes.delete
235
        ).exists()
236

237
    def can_invite_user(self):
1✔
238
        return self.role.role_role_feature_access.filter(
1✔
239
            type=FeatureTypes.user_access,
240
            access=FeatureAccessTypes.invite_user,
241
        ).exists()
242

243
    def __str__(self):
1✔
244
        return f"{self.user.name} - {self.role.name} ({self.administration})"
×
245

246
    class Meta:
1✔
247
        unique_together = ("user", "role", "administration")
1✔
248
        db_table = "user_role"
1✔
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