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

akvo / akvo-mis / #420

17 Oct 2025 03:55AM UTC coverage: 88.647% (+0.01%) from 88.637%
#420

push

coveralls-python

web-flow
Merge pull request #138 from akvo/origin/feature/134-dependency-rule

Origin/feature/134 dependency rule

3505 of 4069 branches covered (86.14%)

Branch coverage included in aggregate %.

7239 of 8051 relevant lines covered (89.91%)

0.9 hits per line

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

94.59
backend/api/v1/v1_forms/models.py
1
import uuid
1✔
2

3
from django.db import models
1✔
4

5
# Create your models here.
6
from api.v1.v1_forms.constants import (
1✔
7
    QuestionTypes,
8
    AttributeTypes,
9
    FormTypes,
10
)
11
from api.v1.v1_users.models import SystemUser
1✔
12

13

14
class Forms(models.Model):
1✔
15
    name = models.TextField()
1✔
16
    version = models.IntegerField(default=1)
1✔
17
    uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
1✔
18
    approval_instructions = models.JSONField(default=None, null=True)
1✔
19
    parent = models.ForeignKey(
1✔
20
        "self",
21
        on_delete=models.CASCADE,
22
        related_name="children",
23
        null=True,
24
        blank=True,
25
    )
26
    type = models.IntegerField(
1✔
27
        choices=FormTypes.FieldStr.items(),
28
        default=FormTypes.registration,
29
    )
30

31
    def __str__(self):
1✔
32
        return self.name
×
33

34
    class Meta:
1✔
35
        db_table = "form"
1✔
36

37

38
class QuestionGroup(models.Model):
1✔
39
    form = models.ForeignKey(
1✔
40
        to=Forms, on_delete=models.CASCADE, related_name="form_question_group"
41
    )
42
    name = models.CharField(max_length=255)
1✔
43
    label = models.TextField(null=True, default=None)
1✔
44
    order = models.BigIntegerField(null=True, default=None)
1✔
45
    repeatable = models.BooleanField(default=False)
1✔
46
    repeat_text = models.CharField(max_length=255, default=None, null=True)
1✔
47

48
    def __str__(self):
1✔
49
        return self.name
×
50

51
    class Meta:
1✔
52
        unique_together = ("form", "name")
1✔
53
        constraints = [
1✔
54
            models.UniqueConstraint(
55
                fields=["form", "name"], name="unique_form_question_group"
56
            )
57
        ]
58
        db_table = "question_group"
1✔
59

60

61
class Questions(models.Model):
1✔
62
    form = models.ForeignKey(
1✔
63
        to=Forms, on_delete=models.CASCADE, related_name="form_questions"
64
    )
65
    question_group = models.ForeignKey(
1✔
66
        to=QuestionGroup,
67
        on_delete=models.CASCADE,
68
        related_name="question_group_question",
69
    )
70
    order = models.BigIntegerField(null=True, default=None)
1✔
71
    label = models.TextField()
1✔
72
    short_label = models.TextField(null=True, default=None)
1✔
73
    name = models.CharField(max_length=255, default=None, null=True)
1✔
74
    type = models.IntegerField(choices=QuestionTypes.FieldStr.items())
1✔
75
    meta = models.BooleanField(default=False)
1✔
76
    required = models.BooleanField(default=True)
1✔
77
    rule = models.JSONField(default=None, null=True)
1✔
78
    dependency = models.JSONField(default=None, null=True)
1✔
79
    dependency_rule = models.CharField(
1✔
80
        max_length=3,
81
        choices=[('AND', 'AND'), ('OR', 'OR')],
82
        null=True,
83
        blank=True,
84
        help_text=(
85
            'Dependency evaluation rule: AND or OR.'
86
            ' Defaults to AND in client logic if not specified.'
87
        )
88
    )
89
    api = models.JSONField(default=None, null=True)
1✔
90
    extra = models.JSONField(default=None, null=True)
1✔
91
    tooltip = models.JSONField(default=None, null=True)
1✔
92
    fn = models.JSONField(default=None, null=True)
1✔
93
    pre = models.JSONField(default=None, null=True)
1✔
94
    display_only = models.BooleanField(default=False, null=True)
1✔
95

96
    def __str__(self):
1✔
97
        return f"[TYPE: {self.type}] {self.label}"
×
98

99
    def to_definition(self):
1✔
100
        options = self.options.values("label", "value")
1✔
101
        return {
1✔
102
            "id": self.id,
103
            "qg_id": self.question_group.id,
104
            "order": (self.order or 0) + 1,
105
            "name": self.name,
106
            "label": self.label,
107
            "short_label": self.short_label,
108
            "type": QuestionTypes.FieldStr.get(self.type),
109
            "required": self.required,
110
            "rule": self.rule,
111
            "dependency": self.dependency,
112
            "dependency_rule": (self.dependency_rule or "AND").upper(),
113
            "options": options,
114
            "extra": self.extra,
115
            "tooltip": self.tooltip,
116
            "fn": self.fn,
117
            "pre": self.pre,
118
            "display_only": self.display_only,
119
            "form_name": self.form.name,
120
        }
121

122
    class Meta:
1✔
123
        unique_together = ("form", "name")
1✔
124
        constraints = [
1✔
125
            models.UniqueConstraint(
126
                fields=["form", "name"], name="unique_form_question"
127
            )
128
        ]
129
        db_table = "question"
1✔
130

131

132
class QuestionOptions(models.Model):
1✔
133
    question = models.ForeignKey(
1✔
134
        to=Questions, on_delete=models.CASCADE, related_name="options"
135
    )
136
    order = models.BigIntegerField(null=True, default=None)
1✔
137
    label = models.TextField(default=None, null=True)
1✔
138
    value = models.CharField(max_length=255, default=None, null=True)
1✔
139
    other = models.BooleanField(default=False)
1✔
140
    color = models.TextField(default=None, null=True)
1✔
141

142
    def __str__(self):
1✔
143
        return self.value
×
144

145
    class Meta:
1✔
146
        unique_together = ("question", "value")
1✔
147
        constraints = [
1✔
148
            models.UniqueConstraint(
149
                fields=["question", "value"], name="unique_question_option"
150
            )
151
        ]
152
        db_table = "option"
1✔
153

154

155
class UserForms(models.Model):
1✔
156
    user = models.ForeignKey(
1✔
157
        to=SystemUser, on_delete=models.CASCADE, related_name="user_form"
158
    )
159
    form = models.ForeignKey(
1✔
160
        to=Forms, on_delete=models.CASCADE, related_name="form_user"
161
    )
162

163
    def __str__(self):
1✔
164
        return self.user.email
×
165

166
    class Meta:
1✔
167
        unique_together = ("user", "form")
1✔
168
        db_table = "user_form"
1✔
169

170

171
class QuestionAttribute(models.Model):
1✔
172
    name = models.TextField(null=True, default=None)
1✔
173
    question = models.ForeignKey(
1✔
174
        to=Questions,
175
        on_delete=models.CASCADE,
176
        related_name="question_question_attribute",
177
    )
178
    attribute = models.IntegerField(choices=AttributeTypes.FieldStr.items())
1✔
179
    options = models.JSONField(default=None, null=True)
1✔
180

181
    def __str__(self):
1✔
182
        return self.name
×
183

184
    class Meta:
1✔
185
        unique_together = ("name", "question", "attribute", "options")
1✔
186
        db_table = "question_attribute"
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

© 2026 Coveralls, Inc