[Tests] fix number of expected queries
[mygpo.git] / mygpo / web / utils.py
bloba18a3e9d053881903815be9d4a06de72afeb0537
1 import re
2 import string
3 import collections
4 from datetime import datetime
6 from django.utils.translation import ungettext
7 from django.views.decorators.cache import never_cache
8 from django.utils.html import strip_tags
9 from django.core.urlresolvers import reverse
10 from django.shortcuts import render
11 from django.http import Http404
13 from babel import Locale, UnknownLocaleError
15 from mygpo.podcasts.models import Podcast
16 from mygpo.core.proxy import proxy_object
19 def get_accepted_lang(request):
20 """ returns a list of language codes accepted by the HTTP request """
22 lang_str = request.META.get('HTTP_ACCEPT_LANGUAGE', '')
23 lang_str = filter(lambda c: c in string.letters+',', lang_str)
24 langs = lang_str.split(',')
25 langs = [s[:2] for s in langs]
26 langs = map(str.strip, langs)
27 langs = filter(None, langs)
28 return list(set(langs))
31 RE_LANG = re.compile('^[a-zA-Z]{2}[-_]?.*$')
34 def sanitize_language_code(lang):
35 return lang[:2].lower()
38 def sanitize_language_codes(ls):
39 """
40 expects a list of language codes and returns a unique lost of the first
41 part of all items. obviously invalid entries are skipped
43 >>> sanitize_language_codes(['de-at', 'de-ch'])
44 ['de']
46 >>> sanitize_language_codes(['de-at', 'en', 'en-gb', '(asdf', 'Deutsch'])
47 ['de', 'en']
48 """
50 ls = [sanitize_language_code(l) for l in ls if l and RE_LANG.match(l)]
51 return list(set(ls))
54 def get_language_names(lang):
55 """
56 Takes a list of language codes and returns a list of tuples
57 with (code, name)
58 """
59 res = {}
60 for l in lang:
61 try:
62 locale = Locale(l)
63 except UnknownLocaleError:
64 continue
66 if locale.display_name:
67 res[l] = locale.display_name
69 return res
72 def get_page_list(start, total, cur, show_max):
73 """
74 returns a list of pages to be linked for navigation in a paginated view
76 >>> get_page_list(1, 100, 1, 10)
77 [1, 2, 3, 4, 5, 6, '...', 98, 99, 100]
79 >>> get_page_list(1, 100, 50, 10)
80 [1, '...', 48, 49, 50, 51, '...', 98, 99, 100]
82 >>> get_page_list(1, 100, 99, 10)
83 [1, '...', 97, 98, 99, 100]
85 >>> get_page_list(1, 3, 2, 10)
86 [1, 2, 3]
87 """
89 if show_max >= (total - start):
90 return range(start, total+1)
92 ps = []
93 if (cur - start) > show_max / 2:
94 ps.extend(range(start, show_max / 4))
95 ps.append('...')
96 ps.extend(range(cur - show_max / 4, cur))
98 else:
99 ps.extend(range(start, cur))
101 ps.append(cur)
103 if (total - cur) > show_max / 2:
104 # for the first pages, show more pages at the beginning
105 add = show_max / 2 - len(ps)
106 ps.extend(range(cur + 1, cur + show_max / 4 + add))
107 ps.append('...')
108 ps.extend(range(total - show_max / 4, total + 1))
110 else:
111 ps.extend(range(cur + 1, total + 1))
113 return ps
116 def process_lang_params(request):
118 lang = request.GET.get('lang', None)
120 if lang is None:
121 langs = get_accepted_lang(request)
122 lang = next(iter(langs), '')
124 return sanitize_language_code(lang)
127 def symbian_opml_changes(podcast):
128 podcast.description = (podcast.title or '') + '\n' + \
129 (podcast.description or '')
130 return podcast
133 @never_cache
134 def maintenance(request, *args, **kwargs):
135 resp = render(request, 'maintenance.html', {})
136 resp.status_code = 503
137 return resp
140 def get_podcast_link_target(podcast, view_name='podcast', add_args=[]):
141 """ Returns the link-target for a Podcast, preferring slugs over Ids
143 automatically distringuishes between relational Podcast objects and
144 CouchDB-based Podcasts """
146 # we prefer slugs
147 if podcast.slug:
148 args = [podcast.slug]
149 view_name = '%s-slug' % view_name
151 # as a fallback we use UUIDs
152 else:
153 args = [podcast.get_id()]
154 view_name = '%s-id' % view_name
156 return reverse(view_name, args=args + add_args)
159 def get_podcast_group_link_target(group, view_name, add_args=[]):
160 """ Returns the link-target for a Podcast group, preferring slugs over Ids
162 automatically distringuishes between relational Podcast objects and
163 CouchDB-based Podcasts """
165 # we prefer slugs
166 if group.slug:
167 args = [group.slug]
168 view_name = '%s-slug-id' % view_name
170 # to keep URLs short, we use use oldids
171 elif group.oldid:
172 args = [group.oldid]
174 # as a fallback we use CouchDB-IDs
175 else:
176 args = [group._id]
177 view_name = '%s-slug-id' % view_name
179 return reverse(view_name, args=args + add_args)
182 def get_episode_link_target(episode, podcast, view_name='episode',
183 add_args=[]):
184 """ Returns the link-target for an Episode, preferring slugs over Ids
186 automatically distringuishes between relational Episode objects and
187 CouchDB-based Episodes """
189 # prefer slugs
190 if episode.slug:
191 args = [podcast.slug, episode.slug]
192 view_name = '%s-slug' % view_name
194 # fallback: UUIDs
195 else:
196 podcast = podcast or episode.podcast
197 args = [podcast.get_id(), episode.get_id()]
198 view_name = '%s-id' % view_name
200 return strip_tags(reverse(view_name, args=args + add_args))
203 def fetch_episode_data(episodes, podcasts={}):
205 if not podcasts:
206 podcast_ids = [episode.podcast for episode in episodes]
207 podcasts = Podcast.objects.filter(id__in=podcast_ids)
208 podcasts = {podcast.id: podcast for podcast in podcasts}
210 def set_podcast(episode):
211 episode = proxy_object(episode)
212 episode.podcast = podcasts.get(episode.podcast, None)
213 return episode
215 return map(set_podcast, episodes)
218 # doesn't include the '@' because it's not stored as part of a twitter handle
219 TWITTER_CHARS = string.ascii_letters + string.digits + '_'
222 def normalize_twitter(s):
223 """ normalize user input that is supposed to be a Twitter handle """
224 return "".join(i for i in s if i in TWITTER_CHARS)
227 CCLICENSE = re.compile(r'http://(www\.)?creativecommons.org/licenses/([a-z-]+)/([0-9.]+)?/?')
228 CCPUBLICDOMAIN = re.compile(r'http://(www\.)?creativecommons.org/licenses/publicdomain/?')
229 LicenseInfo = collections.namedtuple('LicenseInfo', 'name version url')
231 def license_info(license_url):
232 """ Extracts license information from the license URL
234 >>> i = license_info('http://creativecommons.org/licenses/by/3.0/')
235 >>> i.name
236 'CC BY'
237 >>> i.version
238 '3.0'
239 >>> i.url
240 'http://creativecommons.org/licenses/by/3.0/'
242 >>> iwww = license_info('http://www.creativecommons.org/licenses/by/3.0/')
243 >>> i.name == iwww.name and i.version == iwww.version
244 True
246 >>> i = license_info('http://www.creativecommons.org/licenses/publicdomain')
247 >>> i.name
248 'Public Domain'
249 >>> i.version is None
250 True
252 >>> i = license_info('http://example.com/my-own-license')
253 >>> i.name is None
254 True
255 >>> i.version is None
256 True
257 >>> i.url
258 'http://example.com/my-own-license'
260 m = CCLICENSE.match(license_url)
261 if m:
262 _, name, version = m.groups()
263 return LicenseInfo('CC %s' % name.upper(), version, license_url)
265 m = CCPUBLICDOMAIN.match(license_url)
266 if m:
267 return LicenseInfo('Public Domain', None, license_url)
269 return LicenseInfo(None, None, license_url)
272 def check_restrictions(obj):
273 """ checks for known restrictions of the object """
275 restrictions = obj.restrictions.split(',')
276 if "hide" in restrictions:
277 raise Http404
279 if "hide-author" in restrictions:
280 obj.author = None
282 return obj
285 def hours_to_str(hours_total):
286 """ returns a human-readable string representation of some hours
288 >>> hours_to_str(1)
289 u'1 hour'
291 >>> hours_to_str(5)
292 u'5 hours'
294 >>> hours_to_str(100)
295 u'4 days, 4 hours'
297 >>> hours_to_str(960)
298 u'5 weeks, 5 days'
300 >>> hours_to_str(961)
301 u'5 weeks, 5 days, 1 hour'
304 weeks = hours_total / 24 / 7
305 days = hours_total / 24 % 7
306 hours = hours_total % 24
308 strs = []
310 if weeks:
311 strs.append(ungettext('%(weeks)d week', '%(weeks)d weeks', weeks) %
312 { 'weeks': weeks})
314 if days:
315 strs.append(ungettext('%(days)d day', '%(days)d days', days) %
316 { 'days': days})
318 if hours:
319 strs.append(ungettext('%(hours)d hour', '%(hours)d hours', hours) %
320 { 'hours': hours})
322 return ', '.join(strs)