Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/django/forms/utils.py: 46%

120 statements  

« prev     ^ index     » next       coverage.py v7.0.5, created at 2023-01-17 06:13 +0000

1import json 

2import warnings 

3from collections import UserList 

4 

5from django.conf import settings 

6from django.core.exceptions import ValidationError 

7from django.forms.renderers import get_default_renderer 

8from django.utils import timezone 

9from django.utils.deprecation import RemovedInDjango50Warning 

10from django.utils.html import escape, format_html_join 

11from django.utils.safestring import mark_safe 

12from django.utils.translation import gettext_lazy as _ 

13from django.utils.version import get_docs_version 

14 

15 

16def pretty_name(name): 

17 """Convert 'first_name' to 'First name'.""" 

18 if not name: 

19 return "" 

20 return name.replace("_", " ").capitalize() 

21 

22 

23def flatatt(attrs): 

24 """ 

25 Convert a dictionary of attributes to a single string. 

26 The returned string will contain a leading space followed by key="value", 

27 XML-style pairs. In the case of a boolean value, the key will appear 

28 without a value. It is assumed that the keys do not need to be 

29 XML-escaped. If the passed dictionary is empty, then return an empty 

30 string. 

31 

32 The result is passed through 'mark_safe' (by way of 'format_html_join'). 

33 """ 

34 key_value_attrs = [] 

35 boolean_attrs = [] 

36 for attr, value in attrs.items(): 

37 if isinstance(value, bool): 

38 if value: 

39 boolean_attrs.append((attr,)) 

40 elif value is not None: 

41 key_value_attrs.append((attr, value)) 

42 

43 return format_html_join("", ' {}="{}"', sorted(key_value_attrs)) + format_html_join( 

44 "", " {}", sorted(boolean_attrs) 

45 ) 

46 

47 

48DEFAULT_TEMPLATE_DEPRECATION_MSG = ( 

49 'The "default.html" templates for forms and formsets will be removed. These were ' 

50 'proxies to the equivalent "table.html" templates, but the new "div.html" ' 

51 "templates will be the default from Django 5.0. Transitional renderers are " 

52 "provided to allow you to opt-in to the new output style now. See " 

53 "https://docs.djangoproject.com/en/%s/releases/4.1/ for more details" 

54 % get_docs_version() 

55) 

56 

57 

58class RenderableMixin: 

59 def get_context(self): 

60 raise NotImplementedError( 

61 "Subclasses of RenderableMixin must provide a get_context() method." 

62 ) 

63 

64 def render(self, template_name=None, context=None, renderer=None): 

65 renderer = renderer or self.renderer 

66 template = template_name or self.template_name 

67 context = context or self.get_context() 

68 if ( 

69 template == "django/forms/default.html" 

70 or template == "django/forms/formsets/default.html" 

71 ): 

72 warnings.warn( 

73 DEFAULT_TEMPLATE_DEPRECATION_MSG, RemovedInDjango50Warning, stacklevel=2 

74 ) 

75 return mark_safe(renderer.render(template, context)) 

76 

77 __str__ = render 

78 __html__ = render 

79 

80 

81class RenderableFormMixin(RenderableMixin): 

82 def as_p(self): 

83 """Render as <p> elements.""" 

84 return self.render(self.template_name_p) 

85 

86 def as_table(self): 

87 """Render as <tr> elements excluding the surrounding <table> tag.""" 

88 return self.render(self.template_name_table) 

89 

90 def as_ul(self): 

91 """Render as <li> elements excluding the surrounding <ul> tag.""" 

92 return self.render(self.template_name_ul) 

93 

94 def as_div(self): 

95 """Render as <div> elements.""" 

96 return self.render(self.template_name_div) 

97 

98 

99class RenderableErrorMixin(RenderableMixin): 

100 def as_json(self, escape_html=False): 

101 return json.dumps(self.get_json_data(escape_html)) 

102 

103 def as_text(self): 

104 return self.render(self.template_name_text) 

105 

106 def as_ul(self): 

107 return self.render(self.template_name_ul) 

108 

109 

110class ErrorDict(dict, RenderableErrorMixin): 

111 """ 

112 A collection of errors that knows how to display itself in various formats. 

113 

114 The dictionary keys are the field names, and the values are the errors. 

115 """ 

116 

117 template_name = "django/forms/errors/dict/default.html" 

118 template_name_text = "django/forms/errors/dict/text.txt" 

119 template_name_ul = "django/forms/errors/dict/ul.html" 

120 

121 def __init__(self, *args, renderer=None, **kwargs): 

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

123 self.renderer = renderer or get_default_renderer() 

124 

125 def as_data(self): 

126 return {f: e.as_data() for f, e in self.items()} 

127 

128 def get_json_data(self, escape_html=False): 

129 return {f: e.get_json_data(escape_html) for f, e in self.items()} 

130 

131 def get_context(self): 

132 return { 

133 "errors": self.items(), 

134 "error_class": "errorlist", 

135 } 

136 

137 

138class ErrorList(UserList, list, RenderableErrorMixin): 

139 """ 

140 A collection of errors that knows how to display itself in various formats. 

141 """ 

142 

143 template_name = "django/forms/errors/list/default.html" 

144 template_name_text = "django/forms/errors/list/text.txt" 

145 template_name_ul = "django/forms/errors/list/ul.html" 

146 

147 def __init__(self, initlist=None, error_class=None, renderer=None): 

148 super().__init__(initlist) 

149 

150 if error_class is None: 

151 self.error_class = "errorlist" 

152 else: 

153 self.error_class = "errorlist {}".format(error_class) 

154 self.renderer = renderer or get_default_renderer() 

155 

156 def as_data(self): 

157 return ValidationError(self.data).error_list 

158 

159 def copy(self): 

160 copy = super().copy() 

161 copy.error_class = self.error_class 

162 return copy 

163 

164 def get_json_data(self, escape_html=False): 

165 errors = [] 

166 for error in self.as_data(): 

167 message = next(iter(error)) 

168 errors.append( 

169 { 

170 "message": escape(message) if escape_html else message, 

171 "code": error.code or "", 

172 } 

173 ) 

174 return errors 

175 

176 def get_context(self): 

177 return { 

178 "errors": self, 

179 "error_class": self.error_class, 

180 } 

181 

182 def __repr__(self): 

183 return repr(list(self)) 

184 

185 def __contains__(self, item): 

186 return item in list(self) 

187 

188 def __eq__(self, other): 

189 return list(self) == other 

190 

191 def __getitem__(self, i): 

192 error = self.data[i] 

193 if isinstance(error, ValidationError): 

194 return next(iter(error)) 

195 return error 

196 

197 def __reduce_ex__(self, *args, **kwargs): 

198 # The `list` reduce function returns an iterator as the fourth element 

199 # that is normally used for repopulating. Since we only inherit from 

200 # `list` for `isinstance` backward compatibility (Refs #17413) we 

201 # nullify this iterator as it would otherwise result in duplicate 

202 # entries. (Refs #23594) 

203 info = super(UserList, self).__reduce_ex__(*args, **kwargs) 

204 return info[:3] + (None, None) 

205 

206 

207# Utilities for time zone support in DateTimeField et al. 

208 

209 

210def from_current_timezone(value): 

211 """ 

212 When time zone support is enabled, convert naive datetimes 

213 entered in the current time zone to aware datetimes. 

214 """ 

215 if settings.USE_TZ and value is not None and timezone.is_naive(value): 

216 current_timezone = timezone.get_current_timezone() 

217 try: 

218 if not timezone._is_pytz_zone( 

219 current_timezone 

220 ) and timezone._datetime_ambiguous_or_imaginary(value, current_timezone): 

221 raise ValueError("Ambiguous or non-existent time.") 

222 return timezone.make_aware(value, current_timezone) 

223 except Exception as exc: 

224 raise ValidationError( 

225 _( 

226 "%(datetime)s couldn’t be interpreted " 

227 "in time zone %(current_timezone)s; it " 

228 "may be ambiguous or it may not exist." 

229 ), 

230 code="ambiguous_timezone", 

231 params={"datetime": value, "current_timezone": current_timezone}, 

232 ) from exc 

233 return value 

234 

235 

236def to_current_timezone(value): 

237 """ 

238 When time zone support is enabled, convert aware datetimes 

239 to naive datetimes in the current time zone for display. 

240 """ 

241 if settings.USE_TZ and value is not None and timezone.is_aware(value): 

242 return timezone.make_naive(value) 

243 return value