8 from datetime import datetime, timedelta
10 from django import template
11 from django.conf import settings
12 from django.db.models.query import QuerySet
13 from django.urls import reverse
14 from django.utils.encoding import force_str
15 from django.utils.html import strip_tags
16 from django.utils.http import quote
17 from django.utils.safestring import mark_safe
18 from django.utils.text import Truncator
19 from emissions.app_settings import app_settings as emissions_app_settings
20 from emissions.models import Emission, Episode, Focus, NewsItem, SoundFile
21 from emissions.utils import period_program
22 from taggit.models import Tag
24 from panikweb import search, utils
26 register = template.Library()
29 @register.filter(name='zip')
34 @register.inclusion_tag('includes/audio.html', takes_context=True)
35 def audio(context, sound=None, embed=False, display_fragment_name=False):
37 'episode': context.get('episode'),
39 'display_fragment_name': display_fragment_name,
41 'has_offsite_media': bool(settings.OFFSITE_MEDIA_SOUNDS),
45 @register.inclusion_tag('listen/nav.html', takes_context=True)
46 def listen_nav(context, date=None, klass=None):
49 'categories': context.get('categories'),
53 @register.inclusion_tag('news/nav.html', takes_context=True)
54 def news_nav(context, date=None, klass=None):
57 'newsitem': context.get('newsitem'),
58 'categories': context.get('categories'),
59 'news': context.get('news'),
60 'search_query': context.get('search_query'),
64 @register.inclusion_tag('emissions/nav.html', takes_context=True)
65 def emission_nav(context, date=None, klass=None):
68 'categories': context.get('categories'),
69 'episodes': context.get('episodes'),
70 'emission': context.get('emission'),
71 'episode': context.get('episode'),
72 'search_query': context.get('search_query'),
76 @register.inclusion_tag('episodes/inline.html', takes_context=True)
77 def episode_inline(context, date=None, model=None, klass=None):
80 'episode': context.get('episode'),
85 @register.inclusion_tag('episodes/resume.html', takes_context=True)
86 def episode_resume(context, date=None, model=None, klass=None):
90 'episode': context.get('episode'),
95 @register.inclusion_tag('episodes/detail.html', takes_context=True)
96 def episode_detail(context, date=None):
97 soundfiles = SoundFile.objects.select_related().filter(
98 fragment=True, podcastable=True, episode=context.get('episode')
101 'episode': context.get('episode'),
102 'emission': context.get('emission'),
103 'diffusions': context.get('diffusions'),
104 'soundfiles': soundfiles,
106 'topik_pages': context.get('topik_pages'),
110 @register.inclusion_tag('emissions/detail.html', takes_context=True)
111 def emission_detail(context, date=None):
113 'emission': context.get('emission'),
114 'schedules': context.get('schedules'),
118 @register.inclusion_tag('emissions/resume.html', takes_context=True)
119 def emission_resume(context, date=None):
121 'emission': context.get('emission'),
122 'schedules': context.get('schedules'),
126 @register.inclusion_tag('emissions/inline.html', takes_context=True)
127 def emission_inline(context, date=None):
129 'emission': context.get('emission'),
130 'schedules': context.get('schedules'),
134 @register.inclusion_tag('soundfiles/resume.html')
135 def soundfile_resume(soundfile, date=None):
136 return {'soundfile': soundfile, 'date': date}
139 @register.inclusion_tag('includes/player.html', takes_context=True)
140 def player(context, player_id='main', display_emission_subtitle=False, display_episode_subtitle=False):
142 'unique': uuid.uuid4(),
143 'radio_stream_urls': [
144 x for x in settings.RADIO_STREAM_URLS if x.get('player_id', 'main') == player_id
146 'soundfiles': context.get('soundfiles'),
147 'player_id': player_id,
148 'prefix': '' if player_id == 'main' else f'{player_id}-',
149 'display_emission_subtitle': display_emission_subtitle,
150 'display_episode_subtitle': display_episode_subtitle,
154 @register.inclusion_tag('includes/metaNav.html', takes_context=True)
155 def metanav(context):
156 return {'LANGUAGE_CODE': context.get('LANGUAGE_CODE')}
159 @register.inclusion_tag('includes/week.html')
160 def weekview(year=None, week=None, weekday=None, include_nonstop=True):
161 year = year if year else datetime.today().isocalendar()[0]
162 week = week if week else datetime.today().isocalendar()[1]
163 weekday = weekday if weekday is not None else datetime.today().weekday()
165 date = utils.tofirstdayinisoweek(year, week)
167 *date.timetuple()[:3]
168 + (emissions_app_settings.DAY_HOUR_START, emissions_app_settings.DAY_MINUTE_START)
171 program = period_program(date, date + timedelta(days=7), include_nonstop=include_nonstop)
176 'cells': [x for x in program if x.is_on_weekday(day + 1)],
177 'datetime': date + timedelta(days=day),
186 'now': datetime.now(),
187 'current_week': bool(datetime.now() >= date and datetime.now() < date + timedelta(days=7)),
191 @register.inclusion_tag('includes/week-nav.html')
192 def weeknav(year=None, week=None, weekday=None):
193 year = year if year else datetime.today().isocalendar()[0]
194 week = week if week else datetime.today().isocalendar()[1]
195 weekday = weekday if weekday is not None else datetime.today().weekday()
197 date = utils.tofirstdayinisoweek(year, week)
199 *date.timetuple()[:3]
200 + (emissions_app_settings.DAY_HOUR_START, emissions_app_settings.DAY_MINUTE_START)
205 days.append({'datetime': date + timedelta(days=day)})
207 previous_week = date - timedelta(days=7)
208 previous_week_year, previous_week_no = previous_week.isocalendar()[:2]
210 next_week = date + timedelta(days=7)
211 next_week_year, next_week_no = next_week.isocalendar()[:2]
218 'previous_week_year': previous_week_year,
219 'previous_week_no': previous_week_no,
220 'next_week_year': next_week_year,
221 'next_week_no': next_week_no,
225 @register.inclusion_tag('news/inline.html', takes_context=True)
226 def news_inline(context, klass=None, logo=None):
227 return {'content': context.get('content'), 'class': klass, 'logo': logo}
230 @register.inclusion_tag('news/roll.html')
233 'news': Focus.objects.filter(current=True)
234 .select_related('emission', 'newsitem', 'soundfile', 'episode', 'newsitem__category')
235 .order_by('?')[: settings.HOME_FOCUS_COUNT]
241 if isinstance(object, QuerySet):
242 return serialize('json', object)
243 return json.dumps(object)
247 def strreplace(string, args):
248 find = args.split(',')[0]
249 replace = args.split(',')[1]
250 return string.replace(find, replace)
254 def replace(string, args):
255 search = args.split(args[0])[1]
256 replace = args.split(args[0])[2]
258 return re.sub(search, replace, string)
261 def remove_facet(facet_id, url, facet):
262 scheme, netloc, path, query, fragment = list(urllib.parse.urlsplit(str(url)))
263 facet = '%s_exact:%s' % (facet_id, facet)
264 query_string = urllib.parse.parse_qsl(query)
265 query_string = [x for x in query_string if not (x[0] == 'selected_facets' and x[1] == facet)]
266 query = '&'.join(['%s=%s' % x for x in query_string])
267 url = urllib.parse.urlunsplit([scheme, netloc, path, query, None])
268 return re.sub(r'&page=\d+', '', url)
272 def remove_tag_facet(url, facet):
273 return remove_facet('tags', url, facet)
277 def remove_category_facet(url, facet):
278 return remove_facet('categories', url, facet)
282 def remove_news_category_facet(url, facet):
283 return remove_facet('news_categories', url, facet)
286 def with_facet(url, facet_id, facet):
287 scheme, netloc, path, query, fragment = list(urllib.parse.urlsplit(str(url)))
288 criteria = '%s_exact:' % facet_id
289 query_string = urllib.parse.parse_qsl(query)
290 query_string = [x for x in query_string if not (x[0] == 'selected_facets' and x[1].startswith(criteria))]
291 if facet is not None:
292 query_string.append(('selected_facets', criteria + facet))
293 query = '&'.join(['%s=%s' % x for x in query_string])
294 url = urllib.parse.urlunsplit([scheme, netloc, path, query, None])
295 return re.sub(r'&page=\d+', '', url)
299 def with_news_category_facet(url, facet):
300 return with_facet(url, 'news_categories', facet)
304 def without_news_category_facet(url):
305 return with_facet(url, 'news_categories', facet=None)
309 def with_format_facet(url, facet):
310 return with_facet(url, 'format', facet)
314 def without_format_facet(url):
315 return with_facet(url, 'format', facet=None)
319 def remove_format_facet(url, facet):
320 return remove_facet('format', url, facet)
323 def append_facet(facet_id, url, facet):
324 facet = quote(facet, safe='')
327 return re.sub(r'&page=\d+', '', url + '&selected_facets=%s_exact:%s' % (facet_id, facet))
331 def append_tag_facet(url, facet):
332 return append_facet('tags', url, facet)
336 def append_category_facet(url, facet):
337 return append_facet('categories', url, facet)
341 def append_news_category_facet(url, facet):
342 return append_facet('news_categories', url, facet)
346 def append_format_facet(url, facet):
347 return append_facet('format', url, facet)
351 def search_result_template(parser, token):
353 tag_name, result_str = token.split_contents()
355 raise template.TemplateSyntaxError("%r tag requires exactly one argument" % token.contents.split()[0])
356 return FormatSearchResultNode(result_str)
359 class FormatSearchResultNode(template.Node):
360 def __init__(self, result_str):
361 self.result_var = template.Variable(result_str)
363 def render(self, context):
364 result = self.result_var.resolve(context)
365 dir_mapping = {'newsitem': 'news', 'emission': 'emissions', 'episode': 'episodes'}
366 t = template.loader.get_template('%s/search_result.html' % dir_mapping.get(result.model_name))
367 return t.render({'result': result})
370 @register.inclusion_tag('includes/piwik.html')
372 return {'enabled': settings.ENABLE_PIWIK}
376 def rfc822(datetime):
379 return email.utils.formatdate(time.mktime(datetime.timetuple()))
382 @register.inclusion_tag('includes/related.html', takes_context=False)
383 def related_objects(object):
384 sqs = search.MoreLikeThisSearchQuerySet().models(Emission, Episode, NewsItem)
385 return {'more_like_this': sqs.more_like_this(object)[:12]}
389 def random_emissions(count=6):
390 return Emission.objects.filter(archived=False).order_by('?')[:count]
395 return Tag.objects.exclude(taggit_taggeditem_items__isnull=True)
398 @register.inclusion_tag('includes/topik.html', takes_context=True)
399 def topik(context, topik):
400 return {'page': topik}
404 def get_focus_url(object):
406 return reverse('newsitem-view', kwargs={'slug': object.newsitem.slug})
408 return reverse('emission-view', kwargs={'slug': object.emission.slug})
412 kwargs={'slug': object.episode.slug, 'emission_slug': object.episode.emission.slug},
418 'slug': object.soundfile.episode.slug,
419 'emission_slug': object.soundfile.episode.emission.slug,
423 return object.page.get_online_url()
429 return tag.name.lower()
433 def set_absolute_urls(text):
434 text = text.replace('src="/', 'src="%s' % settings.WEBSITE_BASE_URL)
435 text = text.replace('href="/', 'href="%s' % settings.WEBSITE_BASE_URL)
440 def as_absolute_url(url):
441 if url.startswith('/'):
442 url = settings.WEBSITE_BASE_URL + url.lstrip('/')
447 def xml_illegal_fix(text):
448 # django.utils.xmlutils.UnserializableContentError: Control characters are not supported in XML 1.0
449 for i in range(0x20): # remove control characters
451 if char in ('\t', '\r', '\n'):
452 # only allow tab, carriage return and line feed.
454 text = text.replace(char, '')
455 # fffe and ffff are also invalid characters
456 return text.replace('\ufffe', '').replace('\uffff', '')
461 return re.split(r'<hr\s*/?>', text)
466 return url.replace('https://', 'itpc://').replace('http://', 'itpc://')
470 def plain_short_description(text):
471 return mark_safe(Truncator(mark_safe(strip_tags(text))).words(75, truncate="…").replace('"', '"'))