/
article_tags.py
executable file
·314 lines (246 loc) · 9.71 KB
/
article_tags.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
from django import template
from django.core.cache import cache
from django.core.urlresolvers import resolve, reverse, Resolver404
from django.db.models import Count
from articles.models import Article, Tag
from datetime import datetime
import math
register = template.Library()
class GetCategoriesNode(template.Node):
"""
Retrieves a list of live article tags and places it into the context
"""
def __init__(self, varname):
self.varname = varname
def render(self, context):
tags = Tag.objects.all()
context[self.varname] = tags
return ''
def get_article_tags(parser, token):
"""
Retrieves a list of live article tags and places it into the context
"""
args = token.split_contents()
argc = len(args)
try:
assert argc == 3 and args[1] == 'as'
except AssertionError:
raise template.TemplateSyntaxError('get_article_tags syntax: {% get_article_tags as varname %}')
return GetCategoriesNode(args[2])
class GetArticlesNode(template.Node):
"""
Retrieves a set of article objects.
Usage::
{% get_articles 5 as varname %}
{% get_articles 5 as varname asc %}
{% get_articles 1 to 5 as varname %}
{% get_articles 1 to 5 as varname asc %}
"""
def __init__(self, varname, count=None, start=None, end=None, order='desc'):
self.count = count
self.start = start
self.end = end
self.order = order
self.varname = varname.strip()
def render(self, context):
# determine the order to sort the articles
if self.order and self.order.lower() == 'desc':
order = '-publish_date'
else:
order = 'publish_date'
user = context.get('user', None)
# get the live articles in the appropriate order
articles = Article.objects.live(user=user).order_by(order).select_related()
if self.count:
# if we have a number of articles to retrieve, pull the first of them
articles = articles[:self.count]
else:
# get a range of articles
articles = articles[(int(self.start) - 1):int(self.end)]
# don't send back a list when we really don't need/want one
if len(articles) == 1 and not self.start: articles = articles[0]
# put the article(s) into the context
context[self.varname] = articles
return ''
def get_articles(parser, token):
"""
Retrieves a list of Article objects for use in a template.
"""
args = token.split_contents()
argc = len(args)
try:
assert argc in (4,6) or (argc in (5,7) and args[-1].lower() in ('desc', 'asc'))
except AssertionError:
raise template.TemplateSyntaxError('Invalid get_articles syntax.')
# determine what parameters to use
order = 'desc'
count = start = end = varname = None
if argc == 4: t, count, a, varname = args
elif argc == 5: t, count, a, varname, order = args
elif argc == 6: t, start, t, end, a, varname = args
elif argc == 7: t, start, t, end, a, varname, order = args
return GetArticlesNode(count=count,
start=start,
end=end,
order=order,
varname=varname)
class GetArticleArchivesNode(template.Node):
"""
Retrieves a list of years and months in which articles have been posted.
"""
def __init__(self, varname):
self.varname = varname
def render(self, context):
cache_key = 'article_archive_list'
dt_archives = cache.get(cache_key)
if dt_archives is None:
archives = {}
user = context.get('user', None)
# iterate over all live articles
for article in Article.objects.live(user=user).select_related():
pub = article.publish_date
# see if we already have an article in this year
if not archives.has_key(pub.year):
# if not, initialize a dict for the year
archives[pub.year] = {}
# make sure we know that we have an article posted in this month/year
archives[pub.year][pub.month] = True
dt_archives = []
# now sort the years, so they don't appear randomly on the page
years = list(int(k) for k in archives.keys())
years.sort()
# more recent years will appear first in the resulting collection
years.reverse()
# iterate over all years
for year in years:
# sort the months of this year in which articles were posted
m = list(int(k) for k in archives[year].keys())
m.sort()
# now create a list of datetime objects for each month/year
months = [datetime(year, month, 1) for month in m]
# append this list to our final collection
dt_archives.append( ( year, tuple(months) ) )
cache.set(cache_key, dt_archives)
# put our collection into the context
context[self.varname] = dt_archives
return ''
def get_article_archives(parser, token):
"""
Retrieves a list of years and months in which articles have been posted.
"""
args = token.split_contents()
argc = len(args)
try:
assert argc == 3 and args[1] == 'as'
except AssertionError:
raise template.TemplateSyntaxError('get_article_archives syntax: {% get_article_archives as varname %}')
return GetArticleArchivesNode(args[2])
class DivideObjectListByNode(template.Node):
"""
Divides an object list by some number to determine now many objects will
fit into, say, a column.
"""
def __init__(self, object_list, divisor, varname):
self.object_list = template.Variable(object_list)
self.divisor = template.Variable(divisor)
self.varname = varname
def render(self, context):
# get the actual object list from the context
object_list = self.object_list.resolve(context)
# get the divisor from the context
divisor = int(self.divisor.resolve(context))
# make sure we don't divide by 0 or some negative number!!!!!!
assert divisor > 0
context[self.varname] = int(math.ceil(len(object_list) / float(divisor)))
return ''
def divide_object_list(parser, token):
"""
Divides an object list by some number to determine now many objects will
fit into, say, a column.
"""
args = token.split_contents()
argc = len(args)
try:
assert argc == 6 and args[2] == 'by' and args[4] == 'as'
except AssertionError:
raise template.TemplateSyntaxError('divide_object_list syntax: {% divide_object_list object_list by divisor as varname %}')
return DivideObjectListByNode(args[1], args[3], args[5])
class GetPageURLNode(template.Node):
"""
Determines the URL of a pagination page link based on the page from which
this tag is called.
"""
def __init__(self, page_num, varname=None):
self.page_num = template.Variable(page_num)
self.varname = varname
def render(self, context):
url = None
# get the page number we're linking to from the context
page_num = self.page_num.resolve(context)
try:
# determine what view we are using based upon the path of this page
view, args, kwargs = resolve(context['request'].path)
except (Resolver404, KeyError):
raise ValueError('Invalid pagination page.')
else:
# set the page parameter for this view
kwargs['page'] = page_num
# get the new URL from Django
url = reverse(view, args=args, kwargs=kwargs)
if self.varname:
# if we have a varname, put the URL into the context and return nothing
context[self.varname] = url
return ''
# otherwise, return the URL directly
return url
def get_page_url(parser, token):
"""
Determines the URL of a pagination page link based on the page from which
this tag is called.
"""
args = token.split_contents()
argc = len(args)
varname = None
try:
assert argc in (2, 4)
except AssertionError:
raise template.TemplateSyntaxError('get_page_url syntax: {% get_page_url page_num as varname %}')
if argc == 4: varname = args[3]
return GetPageURLNode(args[1], varname)
class TagCloudNode(template.Node):
def __init__(self, varname):
self.varname = varname
def render(self, context):
context[self.varname] = tags
def tag_cloud():
"""Provides the tags with a "weight" attribute to build a tag cloud"""
cache_key = 'tag_cloud_tags'
tags = cache.get(cache_key)
if tags is None:
MAX_WEIGHT = 7
tags = Tag.objects.annotate(count=Count('article'))
if len(tags) == 0:
# go no further
return {}
min_count = max_count = tags[0].article_set.count()
for tag in tags:
if tag.count < min_count:
min_count = tag.count
if max_count < tag.count:
max_count = tag.count
# calculate count range, and avoid dbz
_range = float(max_count - min_count)
if _range == 0.0:
_range = 1.0
# calculate tag weights
for tag in tags:
tag.weight = int(MAX_WEIGHT * (tag.count - min_count) / _range)
cache.set(cache_key, tags)
return {'tags': tags}
# register dem tags!
register.tag(get_articles)
register.tag(get_article_tags)
register.tag(get_article_archives)
register.tag(divide_object_list)
register.tag(get_page_url)
register.inclusion_tag('articles/_tag_cloud.html')(tag_cloud)