Coverage for fields.py: 0%

56 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-22 07:15 +0000

1from __future__ import annotations 

2 

3import warnings 

4 

5# from collections import UserDict 

6from typing import Any, Sequence 

7 

8from django.conf import settings 

9from django.db.models import JSONField 

10from django.utils import translation 

11 

12 

13class 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 

67class 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 

130class 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 

148if __name__ == "__main__": 

149 import doctest 

150 

151 doctest.testmod()