Coverage for website/newsletters/templatetags/listutil.py: 84.38%

52 statements  

« prev     ^ index     » next       coverage.py v7.6.7, created at 2025-08-14 10:31 +0000

1"""Template filters to partition lists into rows or columns. 

2 

3From https://djangosnippets.org/snippets/401/ 

4 

5A common use-case is for splitting a list into a table with columns:: 

6 

7 {% load partition %} 

8 <table> 

9 {% for row in mylist|columns:3 %} 

10 <tr> 

11 {% for item in row %} 

12 <td>{{ item }}</td> 

13 {% endfor %} 

14 </tr> 

15 {% endfor %} 

16 </table> 

17""" 

18 

19from django.template import Library 

20 

21register = Library() 

22 

23 

24def rows(thelist, n): 

25 """Break a list into ``n`` rows, filling up each row to the maximum equal length possible. 

26 

27 For example:: 

28 

29 >>> l = range(10) 

30 

31 >>> rows(l, 2) 

32 [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]] 

33 

34 >>> rows(l, 3) 

35 [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9]] 

36 

37 >>> rows(l, 4) 

38 [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]] 

39 

40 >>> rows(l, 5) 

41 [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]] 

42 

43 >>> rows(l, 9) 

44 [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9], [], [], [], []] 

45 

46 # This filter will always return `n` rows, even if some are empty: 

47 >>> rows(range(2), 3) 

48 [[0], [1], []] 

49 """ 

50 try: 

51 n = int(n) 

52 thelist = list(thelist) 

53 except (ValueError, TypeError): 

54 return [thelist] 

55 list_len = len(thelist) 

56 split = list_len // n 

57 

58 if list_len % n != 0: 

59 split += 1 

60 return [thelist[split * i : split * (i + 1)] for i in range(n)] 

61 

62 

63def rows_distributed(thelist, n): 

64 """Break a list into ``n`` rows, distributing columns as evenly as possible across the rows. 

65 

66 For example:: 

67 

68 >>> l = range(10) 

69 

70 >>> rows_distributed(l, 2) 

71 [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]] 

72 

73 >>> rows_distributed(l, 3) 

74 [[0, 1, 2, 3], [4, 5, 6], [7, 8, 9]] 

75 

76 >>> rows_distributed(l, 4) 

77 [[0, 1, 2], [3, 4, 5], [6, 7], [8, 9]] 

78 

79 >>> rows_distributed(l, 5) 

80 [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]] 

81 

82 >>> rows_distributed(l, 9) 

83 [[0, 1], [2], [3], [4], [5], [6], [7], [8], [9]] 

84 

85 # This filter will always return `n` rows, even if some are empty: 

86 >>> rows_distributed(range(2), 3) 

87 [[0], [1], []] 

88 """ 

89 try: 

90 n = int(n) 

91 thelist = list(thelist) 

92 except (ValueError, TypeError): 

93 return [thelist] 

94 list_len = len(thelist) 

95 split = list_len // n 

96 

97 remainder = list_len % n 

98 offset = 0 

99 r = [] 

100 for i in range(n): 

101 if remainder: 

102 start, end = (split + 1) * i, (split + 1) * (i + 1) 

103 else: 

104 start, end = split * i + offset, split * (i + 1) + offset 

105 r.append(thelist[start:end]) 

106 if remainder: 

107 remainder -= 1 

108 offset += 1 

109 return r 

110 

111 

112def columns(thelist, n): 

113 """Break a list into ``n`` columns, filling up each column to the maximum equal length possible. 

114 

115 For example: 

116 

117 >>> from pprint import pprint 

118 >>> for i in range(7, 11): 

119 ... print('%sx%s:' % (i, 3)) 

120 ... pprint(columns(range(i), 3), width=20) 

121 7x3: 

122 [[0, 3, 6], 

123 [1, 4], 

124 [2, 5]] 

125 8x3: 

126 [[0, 3, 6], 

127 [1, 4, 7], 

128 [2, 5]] 

129 9x3: 

130 [[0, 3, 6], 

131 [1, 4, 7], 

132 [2, 5, 8]] 

133 10x3: 

134 [[0, 4, 8], 

135 [1, 5, 9], 

136 [2, 6], 

137 [3, 7]] 

138 

139 # Note that this filter does not guarantee that `n` columns will be 

140 # present: 

141 >>> pprint(columns(range(4), 3), width=10) 

142 [[0, 2], 

143 [1, 3]] 

144 """ 

145 try: 

146 n = int(n) 

147 thelist = list(thelist) 

148 except (ValueError, TypeError): 

149 return [thelist] 

150 list_len = len(thelist) 

151 split = list_len // n 

152 if list_len % n != 0: 

153 split += 1 

154 return [thelist[i::split] for i in range(split)] 

155 

156 

157register.filter(rows) 

158register.filter(rows_distributed) 

159register.filter(columns) 

160 

161 

162def _test(): 

163 import doctest 

164 

165 doctest.testmod() 

166 

167 

168if __name__ == "__main__": 168 ↛ 169line 168 didn't jump to line 169 because the condition on line 168 was never true

169 _test()