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