Coverage for src/lektor_ng/types/multi.py: 22%

102 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-08-02 20:38 +0000

1import traceback 

2 

3from lektor_ng.constants import PRIMARY_ALT 

4from lektor_ng.environment.expressions import Expression, FormatExpression 

5from lektor_ng.i18n import get_i18n_block 

6from lektor_ng.types.base import Type 

7 

8 

9def _reflow_and_split_labels(labels): 

10 rv = [] 

11 for lang, string in labels.items(): 

12 for idx, item in enumerate(string.split(",")): 

13 try: 

14 d = rv[idx] 

15 except LookupError: 

16 d = {} 

17 rv.append(d) 

18 d[lang] = item.strip() 

19 return rv 

20 

21 

22def _parse_choices(options): 

23 s = options.get("choices") 

24 if not s: 

25 return None 

26 

27 choices = [] 

28 items = s.split(",") 

29 user_labels = get_i18n_block(options, "choice_labels") 

30 implied_labels = [] 

31 

32 for item in items: 

33 if "=" in item: 

34 choice, value = item.split("=", 1) 

35 choice = choice.strip() 

36 if choice.isdigit(): 

37 choice = int(choice) 

38 implied_labels.append(value.strip()) 

39 choices.append(choice) 

40 else: 

41 choices.append(item.strip()) 

42 implied_labels.append(item.strip()) 

43 

44 if user_labels: 

45 rv = list(zip(choices, _reflow_and_split_labels(user_labels), strict=False)) 

46 else: 

47 rv = [(key, {"en": label}) for key, label in zip(choices, implied_labels, strict=False)] 

48 

49 return rv 

50 

51 

52class ChoiceSource: 

53 def __init__(self, env, options): 

54 source = options.get("source") 

55 if source is not None: 

56 self.source = Expression(env, source) 

57 self.choices = None 

58 item_key = options.get("item_key") or "{{ this._id }}" 

59 item_label = options.get("item_label") 

60 else: 

61 self.source = None 

62 self.choices = _parse_choices(options) 

63 item_key = options.get("item_key") or "{{ this.0 }}" 

64 item_label = options.get("item_label") 

65 self.item_key = FormatExpression(env, item_key) 

66 if item_label is not None: 

67 item_label = FormatExpression(env, item_label) 

68 self.item_label = item_label 

69 

70 @property 

71 def has_choices(self): 

72 return self.source is not None or self.choices is not None 

73 

74 def iter_choices(self, pad, record=None, alt=PRIMARY_ALT): 

75 values = {} 

76 if record is not None: 

77 values["record"] = record 

78 if self.choices is not None: 

79 iterable = self.choices 

80 else: 

81 try: 

82 iterable = self.source.evaluate(pad, alt=alt, values=values) 

83 except Exception: 

84 traceback.print_exc() 

85 iterable = () 

86 

87 for item in iterable or (): 

88 key = self.item_key.evaluate(pad, this=item, alt=alt, values=values) 

89 

90 # If there is a label expression, use it. Since in that case 

91 # we only have one language to fill in, we fill it in for the 

92 # default language 

93 if self.item_label is not None: 

94 label = {"en": self.item_label.evaluate(pad, this=item, alt=alt, values=values)} 

95 

96 # Otherwise we create a proper internationalized key out of 

97 # our target label 

98 else: 

99 if isinstance(item, (tuple, list)) and len(item) == 2: 

100 label = item[1] 

101 elif hasattr(item, "get_record_label_i18n"): 

102 label = item.get_record_label_i18n() 

103 else: 

104 label = {"en": item["_id"]} 

105 

106 yield key, label 

107 

108 

109class MultiType(Type): 

110 def __init__(self, env, options): 

111 Type.__init__(self, env, options) 

112 self.source = ChoiceSource(env, options) 

113 

114 def get_labels(self, pad, record=None, alt=PRIMARY_ALT): 

115 return dict(self.source.iter_choices(pad, record, alt)) 

116 

117 def to_json(self, pad, record=None, alt=PRIMARY_ALT): 

118 rv = Type.to_json(self, pad, record, alt) 

119 if self.source.has_choices: 

120 rv["choices"] = list(self.source.iter_choices(pad, record, alt)) 

121 return rv 

122 

123 

124class SelectType(MultiType): 

125 widget = "select" 

126 

127 def value_from_raw(self, raw): 

128 if raw.value is None: 

129 return raw.missing_value("Missing select value") 

130 return raw.value 

131 

132 

133class CheckboxesType(MultiType): 

134 widget = "checkboxes" 

135 

136 def value_from_raw(self, raw): 

137 rv = [x.strip() for x in (raw.value or "").split(",")] 

138 if rv == [""]: 

139 rv = [] 

140 return rv