Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/mptt/templatetags/mptt_tags.py: 21%

119 statements  

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

1""" 

2Template tags for working with lists of model instances which represent 

3trees. 

4""" 

5from django import template 

6from django.apps import apps 

7from django.core.exceptions import FieldDoesNotExist 

8from django.utils.encoding import force_str 

9from django.utils.safestring import mark_safe 

10from django.utils.translation import gettext as _ 

11 

12from mptt.utils import drilldown_tree_for_node, get_cached_trees, tree_item_iterator 

13 

14 

15register = template.Library() 

16 

17 

18# ## ITERATIVE TAGS 

19 

20 

21class FullTreeForModelNode(template.Node): 

22 def __init__(self, model, context_var): 

23 self.model = model 

24 self.context_var = context_var 

25 

26 def render(self, context): 

27 cls = apps.get_model(*self.model.split(".")) 

28 if cls is None: 

29 raise template.TemplateSyntaxError( 

30 _("full_tree_for_model tag was given an invalid model: %s") % self.model 

31 ) 

32 context[self.context_var] = cls._tree_manager.all() 

33 return "" 

34 

35 

36class DrilldownTreeForNodeNode(template.Node): 

37 def __init__( 

38 self, 

39 node, 

40 context_var, 

41 foreign_key=None, 

42 count_attr=None, 

43 cumulative=False, 

44 all_descendants=False, 

45 ): 

46 self.node = template.Variable(node) 

47 self.context_var = context_var 

48 self.foreign_key = foreign_key 

49 self.count_attr = count_attr 

50 self.cumulative = cumulative 

51 self.all_descendants = all_descendants 

52 

53 def render(self, context): 

54 # Let any VariableDoesNotExist raised bubble up 

55 args = [self.node.resolve(context)] 

56 

57 if self.foreign_key is not None: 

58 app_label, model_name, fk_attr = self.foreign_key.split(".") 

59 cls = apps.get_model(app_label, model_name) 

60 if cls is None: 

61 raise template.TemplateSyntaxError( 

62 _("drilldown_tree_for_node tag was given an invalid model: %s") 

63 % ".".join([app_label, model_name]) 

64 ) 

65 try: 

66 cls._meta.get_field(fk_attr) 

67 except FieldDoesNotExist: 

68 raise template.TemplateSyntaxError( 

69 _( 

70 "drilldown_tree_for_node tag was given an invalid model field: %s" 

71 ) 

72 % fk_attr 

73 ) 

74 args.extend([cls, fk_attr, self.count_attr, self.cumulative]) 

75 

76 context[self.context_var] = drilldown_tree_for_node( 

77 *args, all_descendants=self.all_descendants 

78 ) 

79 return "" 

80 

81 

82@register.tag 

83def full_tree_for_model(parser, token): 

84 """ 

85 Populates a template variable with a ``QuerySet`` containing the 

86 full tree for a given model. 

87 

88 Usage:: 

89 

90 {% full_tree_for_model [model] as [varname] %} 

91 

92 The model is specified in ``[appname].[modelname]`` format. 

93 

94 Example:: 

95 

96 {% full_tree_for_model tests.Genre as genres %} 

97 

98 """ 

99 bits = token.contents.split() 

100 if len(bits) != 4: 

101 raise template.TemplateSyntaxError( 

102 _("%s tag requires three arguments") % bits[0] 

103 ) 

104 if bits[2] != "as": 

105 raise template.TemplateSyntaxError( 

106 _("second argument to %s tag must be 'as'") % bits[0] 

107 ) 

108 return FullTreeForModelNode(bits[1], bits[3]) 

109 

110 

111@register.tag("drilldown_tree_for_node") 

112def do_drilldown_tree_for_node(parser, token): 

113 """ 

114 Populates a template variable with the drilldown tree for a given 

115 node, optionally counting the number of items associated with its 

116 children. 

117 

118 A drilldown tree consists of a node's ancestors, itself and its 

119 immediate children or all descendants. For example, a drilldown tree 

120 for a book category "Personal Finance" might look something like:: 

121 

122 Books 

123 Business, Finance & Law 

124 Personal Finance 

125 Budgeting (220) 

126 Financial Planning (670) 

127 

128 Usage:: 

129 

130 {% drilldown_tree_for_node [node] as [varname] %} 

131 

132 Extended usage:: 

133 

134 {% drilldown_tree_for_node [node] as [varname] all_descendants %} 

135 {% drilldown_tree_for_node [node] as [varname] count [foreign_key] in [count_attr] %} 

136 {% drilldown_tree_for_node [node] as [varname] cumulative count [foreign_key] in [count_attr] %} 

137 

138 The foreign key is specified in ``[appname].[modelname].[fieldname]`` 

139 format, where ``fieldname`` is the name of a field in the specified 

140 model which relates it to the given node's model. 

141 

142 When this form is used, a ``count_attr`` attribute on each child of 

143 the given node in the drilldown tree will contain a count of the 

144 number of items associated with it through the given foreign key. 

145 

146 If cumulative is also specified, this count will be for items 

147 related to the child node and all of its descendants. 

148 

149 Examples:: 

150 

151 {% drilldown_tree_for_node genre as drilldown %} 

152 {% drilldown_tree_for_node genre as drilldown count tests.Game.genre in game_count %} 

153 {% drilldown_tree_for_node genre as drilldown cumulative count tests.Game.genre in game_count %} 

154 

155 """ # noqa 

156 bits = token.contents.split() 

157 len_bits = len(bits) 

158 if len_bits not in (4, 5, 8, 9, 10): 

159 raise template.TemplateSyntaxError( 

160 _("%s tag requires either three, four, seven, eight, or nine arguments") 

161 % bits[0] 

162 ) 

163 if bits[2] != "as": 

164 raise template.TemplateSyntaxError( 

165 _("second argument to %s tag must be 'as'") % bits[0] 

166 ) 

167 

168 all_descendants = False 

169 if len_bits > 4: 

170 if bits[4] == "all_descendants": 

171 len_bits -= 1 

172 bits.pop(4) 

173 all_descendants = True 

174 

175 if len_bits == 8: 

176 if bits[4] != "count": 

177 raise template.TemplateSyntaxError( 

178 _( 

179 "if seven arguments are given, fourth argument to %s tag must be 'with'" 

180 ) 

181 % bits[0] 

182 ) 

183 if bits[6] != "in": 

184 raise template.TemplateSyntaxError( 

185 _("if seven arguments are given, sixth argument to %s tag must be 'in'") 

186 % bits[0] 

187 ) 

188 return DrilldownTreeForNodeNode( 

189 bits[1], bits[3], bits[5], bits[7], all_descendants=all_descendants 

190 ) 

191 elif len_bits == 9: 

192 if bits[4] != "cumulative": 

193 raise template.TemplateSyntaxError( 

194 _( 

195 "if eight arguments are given, fourth argument to %s tag must be 'cumulative'" 

196 ) 

197 % bits[0] 

198 ) 

199 if bits[5] != "count": 

200 raise template.TemplateSyntaxError( 

201 _( 

202 "if eight arguments are given, fifth argument to %s tag must be 'count'" 

203 ) 

204 % bits[0] 

205 ) 

206 if bits[7] != "in": 

207 raise template.TemplateSyntaxError( 

208 _( 

209 "if eight arguments are given, seventh argument to %s tag must be 'in'" 

210 ) 

211 % bits[0] 

212 ) 

213 return DrilldownTreeForNodeNode( 

214 bits[1], 

215 bits[3], 

216 bits[6], 

217 bits[8], 

218 cumulative=True, 

219 all_descendants=all_descendants, 

220 ) 

221 else: 

222 return DrilldownTreeForNodeNode( 

223 bits[1], bits[3], all_descendants=all_descendants 

224 ) 

225 

226 

227@register.filter 

228def tree_info(items, features=None): 

229 """ 

230 Given a list of tree items, produces doubles of a tree item and a 

231 ``dict`` containing information about the tree structure around the 

232 item, with the following contents: 

233 

234 new_level 

235 ``True`` if the current item is the start of a new level in 

236 the tree, ``False`` otherwise. 

237 

238 closed_levels 

239 A list of levels which end after the current item. This will 

240 be an empty list if the next item is at the same level as the 

241 current item. 

242 

243 Using this filter with unpacking in a ``{% for %}`` tag, you should 

244 have enough information about the tree structure to create a 

245 hierarchical representation of the tree. 

246 

247 Example:: 

248 

249 {% for genre,structure in genres|tree_info %} 

250 {% if structure.new_level %}<ul><li>{% else %}</li><li>{% endif %} 

251 {{ genre.name }} 

252 {% for level in structure.closed_levels %}</li></ul>{% endfor %} 

253 {% endfor %} 

254 

255 """ 

256 kwargs = {} 

257 if features: 

258 feature_names = features.split(",") 

259 if "ancestors" in feature_names: 

260 kwargs["ancestors"] = True 

261 return tree_item_iterator(items, **kwargs) 

262 

263 

264@register.filter 

265def tree_path(items, separator=" :: "): 

266 """ 

267 Creates a tree path represented by a list of ``items`` by joining 

268 the items with a ``separator``. 

269 

270 Each path item will be coerced to unicode, so a list of model 

271 instances may be given if required. 

272 

273 Example:: 

274 

275 {{ some_list|tree_path }} 

276 {{ some_node.get_ancestors|tree_path:" > " }} 

277 

278 """ 

279 return separator.join(force_str(i) for i in items) 

280 

281 

282# ## RECURSIVE TAGS 

283 

284 

285@register.filter 

286def cache_tree_children(queryset): 

287 """ 

288 Alias to `mptt.utils.get_cached_trees`. 

289 """ 

290 

291 return get_cached_trees(queryset) 

292 

293 

294class RecurseTreeNode(template.Node): 

295 def __init__(self, template_nodes, queryset_var): 

296 self.template_nodes = template_nodes 

297 self.queryset_var = queryset_var 

298 

299 def _render_node(self, context, node): 

300 bits = [] 

301 context.push() 

302 for child in node.get_children(): 

303 bits.append(self._render_node(context, child)) 

304 context["node"] = node 

305 context["children"] = mark_safe("".join(bits)) 

306 rendered = self.template_nodes.render(context) 

307 context.pop() 

308 return rendered 

309 

310 def render(self, context): 

311 queryset = self.queryset_var.resolve(context) 

312 roots = cache_tree_children(queryset) 

313 bits = [self._render_node(context, node) for node in roots] 

314 return "".join(bits) 

315 

316 

317@register.tag 

318def recursetree(parser, token): 

319 """ 

320 Iterates over the nodes in the tree, and renders the contained block for each node. 

321 This tag will recursively render children into the template variable {{ children }}. 

322 Only one database query is required (children are cached for the whole tree) 

323 

324 Usage: 

325 <ul> 

326 {% recursetree nodes %} 

327 <li> 

328 {{ node.name }} 

329 {% if not node.is_leaf_node %} 

330 <ul> 

331 {{ children }} 

332 </ul> 

333 {% endif %} 

334 </li> 

335 {% endrecursetree %} 

336 </ul> 

337 """ 

338 bits = token.contents.split() 

339 if len(bits) != 2: 

340 raise template.TemplateSyntaxError(_("%s tag requires a queryset") % bits[0]) 

341 

342 queryset_var = template.Variable(bits[1]) 

343 

344 template_nodes = parser.parse(("endrecursetree",)) 

345 parser.delete_first_token() 

346 

347 return RecurseTreeNode(template_nodes, queryset_var)