Plain is headed towards 1.0! Subscribe for development updates →

 1import re
 2
 3from plain import models
 4from plain.exceptions import ValidationError
 5
 6
 7def validate_flag_name(value: str) -> None:
 8    if not re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", value):
 9        raise ValidationError(f"{value} is not a valid Python identifier name")
10
11
12@models.register_model
13class FlagResult(models.Model):
14    created_at = models.DateTimeField(auto_now_add=True)
15    updated_at = models.DateTimeField(auto_now=True)
16    flag = models.ForeignKey("Flag", on_delete=models.CASCADE)
17    key = models.CharField(max_length=255)
18    value = models.JSONField()
19
20    model_options = models.Options(
21        constraints=[
22            models.UniqueConstraint(
23                fields=["flag", "key"], name="plainflags_flagresult_unique_key"
24            ),
25        ],
26    )
27
28    def __str__(self) -> str:
29        return self.key
30
31
32@models.register_model
33class Flag(models.Model):
34    created_at = models.DateTimeField(auto_now_add=True)
35    updated_at = models.DateTimeField(auto_now=True)
36    name = models.CharField(max_length=255, validators=[validate_flag_name])
37
38    # Optional description that can be filled in after the flag is used/created
39    description = models.TextField(required=False)
40
41    # To manually disable a flag before completing deleting
42    # (good to disable first to make sure the code doesn't use the flag anymore)
43    enabled = models.BooleanField(default=True)
44
45    # To provide an easier way to see if a flag is still being used
46    used_at = models.DateTimeField(required=False, allow_null=True)
47
48    model_options = models.Options(
49        constraints=[
50            models.UniqueConstraint(
51                fields=["name"], name="plainflags_flag_unique_name"
52            ),
53        ],
54    )
55
56    def __str__(self) -> str:
57        return self.name