Coverage for formkit_ninja/fields.py: 30%

1 from __future__ import annotations

2

3 import warnings

4

5# from collections import UserDict

6 from typing import Any, Sequence

7

8 from django.conf import settings

9 from django.db.models import JSONField

10 from django.utils import translation

11

12

13 class WhitelistedKeysDict(dict):

14 """

15 Allow only certain keys.

16 >>> t = WhitelistedKeysDict("ahi!", default_key="tet")

17 >>> t["tet"]

18 'ahi!'

19 >>> t = WhitelistedKeysDict("fire!", default_key="en")

20 >>> t["en"]

21 'fire!'

22 >>> t = WhitelistedKeysDict({"en": "fire!", "tet": "ahi!"}, permitted_keys={"tet", "en"})

23 >>> t["tet"]

24 'ahi!'

25 >>> t["en"]

26 'fire!'

27 >>> set(t.keys()) == {'tet', 'en'}

28 True

29 >>> t = WhitelistedKeysDict({"en": "fire!", "tet": "ahi!", "noexist": "should-warn"}, permitted_keys={"tet", "en"})

30 >>> "noexist" in t

31 False

32 >>> set(t.keys()) == {"tet", "en"}

33 True

34 """

35

36 default_key: str | None

37 permitted_keys: set[str]

38

39 def __init__(

40 self,

41 dict_=None,

42 /,

43 permitted_keys: set[str] = set(),

44 default_key: str | None = None,

45 **kwargs,

46 ):

47 self.default_key = default_key

48 self.permitted_keys = permitted_keys

49 if default_key:

50 self.permitted_keys.add(default_key)

51 self.data: dict[str, Any] = {}

52 if isinstance(dict_, str):

53 if self.default_key:

54 super().__init__({self.default_key: dict_})

55 return

56 warnings.warn("No default key was set. You must have a default_key to initialize with a string.")

57 super().__init__()

58 return

59 super().__init__(dict_ or {})

60

61 def __setitem__(self, key: str, item: str) -> None:

62 if key in self.permitted_keys:

63 return super().__setitem__(key, item)

64 warnings.warn(f"ignored key not in whitelist: {key}")

65

66

67 class TranslatedValues(WhitelistedKeysDict):

68 """

69 dict rejects keys not in Django's LANGUAGES

70 >>> t = TranslatedValues("ahi!")

71 >>> t["tet"]

72 'ahi!'

73 >>> t = TranslatedValues({"en": "fire!", "tet": "ahi!"})

74 >>> t["tet"]

75 'ahi!'

76 >>> t["en"]

77 'fire!'

78 >>> set(t.keys()) == {'tet', 'en'}

79 True

80 >>> t = TranslatedValues({"en": "fire!", "tet": "ahi!", "noexist": "should-warn"})

81 >>> "noexist" in t

82 False

83 >>> set(t.keys()) == {"tet", "en"}

84 True

85 >>> t.value # Note: only when in Django, if Django language is set to 'tet'

86 'ahi!'

87 """

88

89 def __init__(self, dict_, /, **kwargs):

90 return super().__init__(

91 dict_,

92 permitted_keys=set((lang[0] for lang in getattr(settings, "LANGUAGES", ()))),

93 default_key=translation.get_language(),

94 **kwargs,

95 )

96

97 @property

98 def value(self):

99 """

100 Return the value at the current language, or fallback

101 """

102 return TranslatedValues.get_str(self)

103

104 @staticmethod

105 def get_str(

106 dict_: TranslatedValues,

107 lang: str = translation.get_language(),

108 fallback: Sequence[str] = ("en",),

109 ):

110 """

111 Return the value at the current language, or fallback (static method)

112 """

113 # A newly created value might error out here if the `__str__` ref's the value

114 # hint the user that they may wish to `refresh_from_db`

115 if dict_ is None:

116 return ""

117

118 if isinstance(dict_, str):

119 warnings.warn(

120 "TranslatedValues received a str, you may have a new model which hasn't called `refresh_from_db` yet"

121 )

122 return dict_

123 if len(list(dict_)) == 0:

124 return "empty"

125 for lc in (lang, *fallback, list(dict_)[0]):

126 if lc in dict_:

127 return dict_[lc]

128

129

130 class TranslatedField(JSONField):

131 """

132 When a string is passed to this field

133 it will be saved as a value corresponding

134 to the "LANGUAGE_CODE" key

135 """

136

137 description = "A translated char field storing content as JSON"

138

139 def from_db_value(self, value, expression, connection):

140 value_: dict | None = super().from_db_value(value, expression, connection)

141 return TranslatedValues(value_)

142

143 def to_python(self, value):

144 value_ = super().to_python(value)

145 return TranslatedValues(value_)

146

147

148 if __name__ == "__main__": 148 ↛ 149line 148 didn't jump to line 149 because the condition on line 148 was never true

149 import doctest

150

151 doctest.testmod()