Simple app to add configuration options to a Django project.
1from typing import Sequence, Union
2
3from django.conf import settings
4from django.core.exceptions import ValidationError
5from django.db import models
6from django.utils.translation import gettext_lazy as _
7
8from options.constants import CONVERTER, FILE, STR, TYPE_CHOICES
9from options.helpers import UploadToDir, convert_value
10from options.managers import OptionManager, UserOptionManager
11
12
13class BaseOption(models.Model):
14 """Base model for system options and configurations."""
15
16 name = models.CharField(
17 verbose_name=_("parameter name"), max_length=255, unique=True, db_index=True
18 )
19 public_name = models.CharField(
20 verbose_name=_("public name of the parameter"),
21 max_length=255,
22 unique=False,
23 blank=True,
24 db_index=True,
25 )
26 help_text = models.TextField(verbose_name=_("help text"), blank=True, null=True)
27 type = models.PositiveIntegerField(choices=TYPE_CHOICES, default=STR)
28 value = models.CharField(
29 null=True, blank=True, default=None, max_length=256, verbose_name=_("value")
30 )
31 file = models.FileField(
32 upload_to=UploadToDir("options", random_name=True), null=True, blank=True
33 )
34 is_list = models.BooleanField(default=False)
35 is_public = models.BooleanField(default=False)
36
37 class Meta:
38 abstract = True
39
40 def __str__(self) -> str:
41 return f"{self.public_name}"
42
43 def get_value(self) -> Union[int, str, float, Sequence]:
44 """Gets the value with the proper type. If the type is not
45 valid it would return the default value for the field, to avoid
46 problems with manual database modifications.
47 """
48 # If the option is a file, returns the URL of the file
49 if self.type == FILE and self.file is not None:
50 return self.file.url
51 if not self.is_list:
52 return convert_value(self.value, self.type)
53 else:
54 values = self.value.split(",")
55 return [convert_value(value, self.type) for value in values]
56
57 def clean(self) -> None:
58 """Calls to the converter to check the type conversion. Added exception
59 for lists, to check all values.
60 """
61 try:
62 values = [self.value] if not self.is_list else self.value.split(",")
63 [CONVERTER.get(self.type, str)(value) for value in values]
64 except ValueError:
65 raise ValidationError(_("Invalid value for this type."))
66 # Default public name
67 if not self.public_name:
68 self.public_name = self.name
69
70 def save(self, *args, **kwargs):
71 self.clean()
72 super().save(*args, **kwargs)
73
74
75class Option(BaseOption):
76 """System options and configurations."""
77
78 objects = OptionManager()
79
80 class Meta:
81 ordering = ["public_name"]
82 swappable = "SIMPLE_OPTIONS_OPTION_MODEL"
83
84
85class UserOption(BaseOption):
86 """Custom option for a user."""
87
88 user = models.ForeignKey(
89 settings.AUTH_USER_MODEL, related_name="options", on_delete=models.CASCADE
90 )
91 name = models.CharField(_("parameter name"), max_length=255)
92
93 objects = UserOptionManager()
94
95 class Meta:
96 unique_together = ["user", "name"]
97 ordering = ["public_name"]
98 swappable = "SIMPLE_OPTIONS_USER_OPTION_MODEL"