fix typo
[mygpo.git] / mygpo / web / utils.py
blob8541605bca57bf5ac4efd8755d274b5005d93ec7
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.core.models import Podcast
16 from mygpo.core.proxy import proxy_object
17 from mygpo.db.couchdb.podcast import podcast_by_id, podcasts_to_dict
20 def get_accepted_lang(request):
21 """ returns a list of language codes accepted by the HTTP request """
23 lang_str = request.META.get('HTTP_ACCEPT_LANGUAGE', '')
24 lang_str = filter(lambda c: c in string.letters+',', lang_str)
25 langs = lang_str.split(',')
26 langs = [s[:2] for s in langs]
27 langs = map(str.strip, langs)
28 langs = filter(None, langs)
29 return list(set(langs))
32 RE_LANG = re.compile('^[a-zA-Z]{2}[-_]?.*$')
35 def sanitize_language_code(lang):
36 return lang[:2].lower()
39 def sanitize_language_codes(ls):
40 """
41 expects a list of language codes and returns a unique lost of the first
42 part of all items. obviously invalid entries are skipped
44 >>> sanitize_language_codes(['de-at', 'de-ch'])
45 ['de']
47 >>> sanitize_language_codes(['de-at', 'en', 'en-gb', '(asdf', 'Deutsch'])
48 ['de', 'en']
49 """
51 ls = [sanitize_language_code(l) for l in ls if l and RE_LANG.match(l)]
52 return list(set(ls))
55 def get_language_names(lang):
56 """
57 Takes a list of language codes and returns a list of tuples
58 with (code, name)
59 """
60 res = {}
61 for l in lang:
62 try:
63 locale = Locale(l)
64 except UnknownLocaleError:
65 continue
67 if locale.display_name:
68 res[l] = locale.display_name
70 return res
73 def get_page_list(start, total, cur, show_max):
74 """
75 returns a list of pages to be linked for navigation in a paginated view
77 >>> get_page_list(1, 100, 1, 10)
78 [1, 2, 3, 4, 5, 6, '...', 98, 99, 100]
80 >>> get_page_list(1, 100, 50, 10)
81 [1, '...', 48, 49, 50, 51, '...', 98, 99, 100]
83 >>> get_page_list(1, 100, 99, 10)
84 [1, '...', 97, 98, 99, 100]
86 >>> get_page_list(1, 3, 2, 10)
87 [1, 2, 3]
88 """
90 if show_max >= (total - start):
91 return range(start, total+1)
93 ps = []
94 if (cur - start) > show_max / 2:
95 ps.extend(range(start, show_max / 4))
96 ps.append('...')
97 ps.extend(range(cur - show_max / 4, cur))
99 else:
100 ps.extend(range(start, cur))
102 ps.append(cur)
104 if (total - cur) > show_max / 2:
105 # for the first pages, show more pages at the beginning
106 add = show_max / 2 - len(ps)
107 ps.extend(range(cur + 1, cur + show_max / 4 + add))
108 ps.append('...')
109 ps.extend(range(total - show_max / 4, total + 1))
111 else:
112 ps.extend(range(cur + 1, total + 1))
114 return ps
117 def process_lang_params(request):
119 lang = request.GET.get('lang', None)
121 if lang is None:
122 langs = get_accepted_lang(request)
123 lang = next(iter(langs), '')
125 return sanitize_language_code(lang)
128 def symbian_opml_changes(podcast):
129 podcast.description = (podcast.title or '') + '\n' + \
130 (podcast.description or '')
131 return podcast
134 @never_cache
135 def maintenance(request, *args, **kwargs):
136 resp = render(request, 'maintenance.html', {})
137 resp.status_code = 503
138 return resp
141 def get_podcast_link_target(podcast, view_name='podcast', add_args=[]):
142 """ Returns the link-target for a Podcast, preferring slugs over Ids
144 automatically distringuishes between relational Podcast objects and
145 CouchDB-based Podcasts """
147 from mygpo.core.models import Podcast
149 # we prefer slugs
150 if podcast.slug:
151 args = [podcast.slug]
152 view_name = '%s-slug-id' % view_name
154 # as a fallback we use CouchDB-IDs
155 else:
156 args = [podcast.get_id()]
157 view_name = '%s-slug-id' % view_name
159 return reverse(view_name, args=args + add_args)
162 def get_podcast_group_link_target(group, view_name, add_args=[]):
163 """ Returns the link-target for a Podcast group, preferring slugs over Ids
165 automatically distringuishes between relational Podcast objects and
166 CouchDB-based Podcasts """
168 from mygpo.core.models import PodcastGroup
170 # we prefer slugs
171 if group.slug:
172 args = [group.slug]
173 view_name = '%s-slug-id' % view_name
175 # to keep URLs short, we use use oldids
176 elif group.oldid:
177 args = [group.oldid]
179 # as a fallback we use CouchDB-IDs
180 else:
181 args = [group._id]
182 view_name = '%s-slug-id' % view_name
184 return reverse(view_name, args=args + add_args)
187 def get_episode_link_target(episode, podcast, view_name='episode',
188 add_args=[]):
189 """ Returns the link-target for an Episode, preferring slugs over Ids
191 automatically distringuishes between relational Episode objects and
192 CouchDB-based Episodes """
194 from mygpo.core.models import Podcast
196 # prefer slugs
197 if episode.slug:
198 args = [podcast.slug or podcast.get_id(), episode.slug]
199 view_name = '%s-slug-id' % view_name
201 # for short URLs, prefer oldids over CouchDB-IDs
202 elif episode.oldid:
203 args = [episode.oldid]
205 # fallback: CouchDB-IDs
206 else:
207 if not podcast:
208 if isinstance(episode.podcast, Podcast):
209 podcast = episode.podcast
210 elif isinstance(episode.podcast, basestring):
211 podcast = podcast_by_id(episode.podcast)
213 args = [podcast.slug or podcast.get_id(), episode._id]
214 view_name = '%s-slug-id' % view_name
216 return strip_tags(reverse(view_name, args=args + add_args))
219 def fetch_episode_data(episodes, podcasts={}):
221 if not podcasts:
222 podcast_ids = [episode.podcast for episode in episodes]
223 podcasts = podcasts_to_dict(podcast_ids)
225 def set_podcast(episode):
226 episode = proxy_object(episode)
227 episode.podcast = podcasts.get(episode.podcast, None)
228 return episode
230 return map(set_podcast, episodes)
233 # doesn't include the '@' because it's not stored as part of a twitter handle
234 TWITTER_CHARS = string.ascii_letters + string.digits + '_'
237 def normalize_twitter(s):
238 """ normalize user input that is supposed to be a Twitter handle """
239 return "".join(i for i in s if i in TWITTER_CHARS)
242 CCLICENSE = re.compile(r'http://(www\.)?creativecommons.org/licenses/([a-z-]+)/([0-9.]+)?/?')
243 CCPUBLICDOMAIN = re.compile(r'http://(www\.)?creativecommons.org/licenses/publicdomain/?')
244 LicenseInfo = collections.namedtuple('LicenseInfo', 'name version url')
246 def license_info(license_url):
247 """ Extracts license information from the license URL
249 >>> i = license_info('http://creativecommons.org/licenses/by/3.0/')
250 >>> i.name
251 'CC BY'
252 >>> i.version
253 '3.0'
254 >>> i.url
255 'http://creativecommons.org/licenses/by/3.0/'
257 >>> iwww = license_info('http://www.creativecommons.org/licenses/by/3.0/')
258 >>> i.name == iwww.name and i.version == iwww.version
259 True
261 >>> i = license_info('http://www.creativecommons.org/licenses/publicdomain')
262 >>> i.name
263 'Public Domain'
264 >>> i.version is None
265 True
267 >>> i = license_info('http://example.com/my-own-license')
268 >>> i.name is None
269 True
270 >>> i.version is None
271 True
272 >>> i.url
273 'http://example.com/my-own-license'
275 m = CCLICENSE.match(license_url)
276 if m:
277 _, name, version = m.groups()
278 return LicenseInfo('CC %s' % name.upper(), version, license_url)
280 m = CCPUBLICDOMAIN.match(license_url)
281 if m:
282 return LicenseInfo('Public Domain', None, license_url)
284 return LicenseInfo(None, None, license_url)
287 def check_restrictions(obj):
288 """ checks for known restrictions of the object """
289 if "hide" in obj.restrictions:
290 raise Http404
292 if "hide-author" in obj.restrictions:
293 obj.author = None
295 return obj
298 def hours_to_str(hours_total):
299 """ returns a human-readable string representation of some hours
301 >>> hours_to_str(1)
302 u'1 hour'
304 >>> hours_to_str(5)
305 u'5 hours'
307 >>> hours_to_str(100)
308 u'4 days, 4 hours'
310 >>> hours_to_str(960)
311 u'5 weeks, 5 days'
313 >>> hours_to_str(961)
314 u'5 weeks, 5 days, 1 hour'
317 weeks = hours_total / 24 / 7
318 days = hours_total / 24 % 7
319 hours = hours_total % 24
321 strs = []
323 if weeks:
324 strs.append(ungettext('%(weeks)d week', '%(weeks)d weeks', weeks) %
325 { 'weeks': weeks})
327 if days:
328 strs.append(ungettext('%(days)d day', '%(days)d days', days) %
329 { 'days': days})
331 if hours:
332 strs.append(ungettext('%(hours)d hour', '%(hours)d hours', hours) %
333 { 'hours': hours})
335 return ', '.join(strs)