Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django_filters/widgets.py: 33%

157 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1from collections.abc import Iterable 

2from copy import deepcopy 

3from itertools import chain 

4from re import search, sub 

5 

6from django import forms 

7from django.db.models.fields import BLANK_CHOICE_DASH 

8from django.forms.utils import flatatt 

9from django.utils.datastructures import MultiValueDict 

10from django.utils.encoding import force_str 

11from django.utils.http import urlencode 

12from django.utils.safestring import mark_safe 

13from django.utils.translation import gettext as _ 

14 

15 

16class LinkWidget(forms.Widget): 

17 def __init__(self, attrs=None, choices=()): 

18 super().__init__(attrs) 

19 

20 self.choices = choices 

21 

22 def value_from_datadict(self, data, files, name): 

23 value = super().value_from_datadict(data, files, name) 

24 self.data = data 

25 return value 

26 

27 def render(self, name, value, attrs=None, choices=(), renderer=None): 

28 if not hasattr(self, "data"): 

29 self.data = {} 

30 if value is None: 

31 value = "" 

32 final_attrs = self.build_attrs(self.attrs, extra_attrs=attrs) 

33 output = ["<ul%s>" % flatatt(final_attrs)] 

34 options = self.render_options(choices, [value], name) 

35 if options: 

36 output.append(options) 

37 output.append("</ul>") 

38 return mark_safe("\n".join(output)) 

39 

40 def render_options(self, choices, selected_choices, name): 

41 selected_choices = set(force_str(v) for v in selected_choices) 

42 output = [] 

43 for option_value, option_label in chain(self.choices, choices): 

44 if isinstance(option_label, (list, tuple)): 

45 for option in option_label: 

46 output.append(self.render_option(name, selected_choices, *option)) 

47 else: 

48 output.append( 

49 self.render_option( 

50 name, selected_choices, option_value, option_label 

51 ) 

52 ) 

53 return "\n".join(output) 

54 

55 def render_option(self, name, selected_choices, option_value, option_label): 

56 option_value = force_str(option_value) 

57 if option_label == BLANK_CHOICE_DASH[0][1]: 

58 option_label = _("All") 

59 data = self.data.copy() 

60 data[name] = option_value 

61 selected = data == self.data or option_value in selected_choices 

62 try: 

63 url = data.urlencode() 

64 except AttributeError: 

65 url = urlencode(data) 

66 return self.option_string() % { 

67 "attrs": selected and ' class="selected"' or "", 

68 "query_string": url, 

69 "label": force_str(option_label), 

70 } 

71 

72 def option_string(self): 

73 return '<li><a%(attrs)s href="?%(query_string)s">%(label)s</a></li>' 

74 

75 

76class SuffixedMultiWidget(forms.MultiWidget): 

77 """ 

78 A MultiWidget that allows users to provide custom suffixes instead of indexes. 

79 

80 - Suffixes must be unique. 

81 - There must be the same number of suffixes as fields. 

82 """ 

83 

84 suffixes = [] 

85 

86 def __init__(self, *args, **kwargs): 

87 super().__init__(*args, **kwargs) 

88 

89 assert len(self.widgets) == len(self.suffixes) 

90 assert len(self.suffixes) == len(set(self.suffixes)) 

91 

92 def suffixed(self, name, suffix): 

93 return "_".join([name, suffix]) if suffix else name 

94 

95 def get_context(self, name, value, attrs): 

96 context = super().get_context(name, value, attrs) 

97 for subcontext, suffix in zip(context["widget"]["subwidgets"], self.suffixes): 

98 subcontext["name"] = self.suffixed(name, suffix) 

99 

100 return context 

101 

102 def value_from_datadict(self, data, files, name): 

103 return [ 

104 widget.value_from_datadict(data, files, self.suffixed(name, suffix)) 

105 for widget, suffix in zip(self.widgets, self.suffixes) 

106 ] 

107 

108 def value_omitted_from_data(self, data, files, name): 

109 return all( 

110 widget.value_omitted_from_data(data, files, self.suffixed(name, suffix)) 

111 for widget, suffix in zip(self.widgets, self.suffixes) 

112 ) 

113 

114 def replace_name(self, output, index): 

115 result = search(r'name="(?P<name>.*)_%d"' % index, output) 

116 name = result.group("name") 

117 name = self.suffixed(name, self.suffixes[index]) 

118 name = 'name="%s"' % name 

119 

120 return sub(r'name=".*_%d"' % index, name, output) 

121 

122 def decompress(self, value): 

123 if value is None: 

124 return [None, None] 

125 return value 

126 

127 

128class RangeWidget(SuffixedMultiWidget): 

129 template_name = "django_filters/widgets/multiwidget.html" 

130 suffixes = ["min", "max"] 

131 

132 def __init__(self, attrs=None): 

133 widgets = (forms.TextInput, forms.TextInput) 

134 super().__init__(widgets, attrs) 

135 

136 def decompress(self, value): 

137 if value: 

138 return [value.start, value.stop] 

139 return [None, None] 

140 

141 

142class DateRangeWidget(RangeWidget): 

143 suffixes = ["after", "before"] 

144 

145 

146class LookupChoiceWidget(SuffixedMultiWidget): 

147 suffixes = [None, "lookup"] 

148 

149 def decompress(self, value): 

150 if value is None: 

151 return [None, None] 

152 return value 

153 

154 

155class BooleanWidget(forms.Select): 

156 """Convert true/false values into the internal Python True/False. 

157 This can be used for AJAX queries that pass true/false from JavaScript's 

158 internal types through. 

159 """ 

160 

161 def __init__(self, attrs=None): 

162 choices = (("", _("Unknown")), ("true", _("Yes")), ("false", _("No"))) 

163 super().__init__(attrs, choices) 

164 

165 def render(self, name, value, attrs=None, renderer=None): 

166 try: 

167 value = {True: "true", False: "false", "1": "true", "0": "false"}[value] 

168 except KeyError: 

169 value = "" 

170 return super().render(name, value, attrs, renderer=renderer) 

171 

172 def value_from_datadict(self, data, files, name): 

173 value = data.get(name, None) 

174 if isinstance(value, str): 174 ↛ 175line 174 didn't jump to line 175, because the condition on line 174 was never true

175 value = value.lower() 

176 

177 return { 

178 "1": True, 

179 "0": False, 

180 "true": True, 

181 "false": False, 

182 True: True, 

183 False: False, 

184 }.get(value, None) 

185 

186 

187class BaseCSVWidget(forms.Widget): 

188 # Surrogate widget for rendering multiple values 

189 surrogate = forms.TextInput 

190 

191 def __init__(self, *args, **kwargs): 

192 super().__init__(*args, **kwargs) 

193 

194 if isinstance(self.surrogate, type): 

195 self.surrogate = self.surrogate() 

196 else: 

197 self.surrogate = deepcopy(self.surrogate) 

198 

199 def _isiterable(self, value): 

200 return isinstance(value, Iterable) and not isinstance(value, str) 

201 

202 def value_from_datadict(self, data, files, name): 

203 value = super().value_from_datadict(data, files, name) 

204 

205 if value is not None: 

206 if value == "": # empty value should parse as an empty list 

207 return [] 

208 return value.split(",") 

209 return None 

210 

211 def render(self, name, value, attrs=None, renderer=None): 

212 if not self._isiterable(value): 

213 value = [value] 

214 

215 if len(value) <= 1: 

216 # delegate to main widget (Select, etc...) if not multiple values 

217 value = value[0] if value else "" 

218 return super().render(name, value, attrs, renderer=renderer) 

219 

220 # if we have multiple values, we need to force render as a text input 

221 # (otherwise, the additional values are lost) 

222 value = [force_str(self.surrogate.format_value(v)) for v in value] 

223 value = ",".join(list(value)) 

224 

225 return self.surrogate.render(name, value, attrs, renderer=renderer) 

226 

227 

228class CSVWidget(BaseCSVWidget, forms.TextInput): 

229 def __init__(self, *args, attrs=None, **kwargs): 

230 super().__init__(*args, attrs, **kwargs) 

231 

232 if attrs is not None: 

233 self.surrogate.attrs.update(attrs) 

234 

235 

236class QueryArrayWidget(BaseCSVWidget, forms.TextInput): 

237 """ 

238 Enables request query array notation that might be consumed by MultipleChoiceFilter 

239 

240 1. Values can be provided as csv string: ?foo=bar,baz 

241 2. Values can be provided as query array: ?foo[]=bar&foo[]=baz 

242 3. Values can be provided as query array: ?foo=bar&foo=baz 

243 

244 Note: Duplicate and empty values are skipped from results 

245 """ 

246 

247 def value_from_datadict(self, data, files, name): 

248 if not isinstance(data, MultiValueDict): 

249 for key, value in data.items(): 

250 # treat value as csv string: ?foo=1,2 

251 if isinstance(value, str): 

252 data[key] = [x.strip() for x in value.rstrip(",").split(",") if x] 

253 data = MultiValueDict(data) 

254 

255 values_list = data.getlist(name, data.getlist("%s[]" % name)) or [] 

256 

257 # apparently its an array, so no need to process it's values as csv 

258 # ?foo=1&foo=2 -> data.getlist(foo) -> foo = [1, 2] 

259 # ?foo[]=1&foo[]=2 -> data.getlist(foo[]) -> foo = [1, 2] 

260 if len(values_list) > 0: 

261 ret = [x for x in values_list if x] 

262 else: 

263 ret = [] 

264 

265 return list(set(ret))