source: telemeta/web/base.py @ 8fdcce4

cremcrem2devdev2diademsfeature/breadcrumbsfeature/ts-0.5feature/ts-0.5.4feature/writecacheformagenericinstru_searchlamlam2mapsv3mergenlivemultiproductionrelease/1.4.4sabiodsecurityserversocialstoragetelecastertestvideo
Last change on this file since 8fdcce4 was 8fdcce4, checked in by yomguy <yomguy@…>, 4 years ago

fix mimetype analyzer (telemeta side), fix csrf for login, (re)add TEMPLATE_CONTEXT_PROCESSORS to settings

  • Property mode set to 100644
File size: 21.4 KB
Line 
1# -*- coding: utf-8 -*-
2# Copyright (C) 2007 Samalyse SARL
3
4# This software is a computer program whose purpose is to backup, analyse,
5# transcode and stream any audio content with its metadata over a web frontend.
6
7# This software is governed by the CeCILL  license under French law and
8# abiding by the rules of distribution of free software.  You can  use,
9# modify and/ or redistribute the software under the terms of the CeCILL
10# license as circulated by CEA, CNRS and INRIA at the following URL
11# "http://www.cecill.info".
12
13# As a counterpart to the access to the source code and  rights to copy,
14# modify and redistribute granted by the license, users are provided only
15# with a limited warranty  and the software's author,  the holder of the
16# economic rights,  and the successive licensors  have only  limited
17# liability.
18
19# In this respect, the user's attention is drawn to the risks associated
20# with loading,  using,  modifying and/or developing or reproducing the
21# software by the user in light of its specific status of free software,
22# that may mean  that it is complicated to manipulate,  and  that  also
23# therefore means  that it is reserved for developers  and  experienced
24# professionals having in-depth computer knowledge. Users are therefore
25# encouraged to load and test the software's suitability as regards their
26# requirements in conditions enabling the security of their systems and/or
27# data to be ensured and,  more generally, to use and operate it in the
28# same conditions as regards security.
29
30# The fact that you are presently reading this means that you have had
31# knowledge of the CeCILL license and that you accept its terms.
32
33# Author: Olivier Guilyardi <olivier@samalyse.com>
34
35import re
36import os
37import sys
38import datetime
39import timeside
40
41from django.template import RequestContext, loader
42from django import template
43from django.http import HttpResponse
44from django.http import Http404
45from django.shortcuts import render_to_response, redirect
46from django.views.generic import list_detail
47from django.conf import settings
48from django.contrib import auth
49from django.contrib.auth.decorators import login_required
50from django.core.context_processors import csrf
51
52from telemeta.models import MediaItem, Location, MediaCollection, EthnicGroup
53from telemeta.models import dublincore, Enumeration
54import telemeta.interop.oai as oai
55from telemeta.interop.oaidatasource import TelemetaOAIDataSource
56from django.core.exceptions import ObjectDoesNotExist
57from telemeta.util.unaccent import unaccent
58from telemeta.util.unaccent import unaccent_icmp
59from telemeta.util.logger import Logger
60from telemeta.cache import TelemetaCache
61import telemeta.web.pages as pages
62
63def render(request, template, data = None, mimetype = None):
64    return render_to_response(template, data, context_instance=RequestContext(request), 
65                              mimetype=mimetype)
66
67def stream_from_processor(decoder, processor):
68    while True:
69        frames,  eod = decoder.process()
70        _chunk,  eod_proc = processor.process(frames, eod)
71        if eod_proc:
72            break
73        yield _chunk
74
75def stream_from_file(file):
76    chunk_size = 0xFFFF
77    f = open(file, 'r')
78    while True:
79        _chunk = f.read(chunk_size)
80        if not len(_chunk):
81            f.close()
82            break
83        yield _chunk
84   
85   
86class WebView(object):
87    """Provide web UI methods"""
88
89    graphers = timeside.core.processors(timeside.api.IGrapher)
90    decoders = timeside.core.processors(timeside.api.IDecoder)
91    encoders= timeside.core.processors(timeside.api.IEncoder)
92    analyzers = timeside.core.processors(timeside.api.IAnalyzer)
93    cache = TelemetaCache(settings.TELEMETA_DATA_CACHE_DIR)
94    cache_export = TelemetaCache(settings.TELEMETA_EXPORT_CACHE_DIR)
95   
96    def index(self, request):
97        """Render the homepage"""
98
99        template = loader.get_template('telemeta/index.html')
100        ids = [id for id in MediaItem.objects.all().values_list('id', flat=True).order_by('?')[0:4]]
101        items = MediaItem.objects.enriched().filter(pk__in=ids)
102
103        context = RequestContext(request, {
104                    'page_content': pages.get_page_content(request, 'parts/home', ignore_slash_issue=True),
105                    'items': items})
106        return HttpResponse(template.render(context))
107
108    def collection_detail(self, request, public_id, template=''):
109        collection = MediaCollection.objects.get(public_id=public_id)
110        return render(request, template, {'collection': collection})
111
112
113    def item_detail(self, request, public_id, template='telemeta/mediaitem_detail.html'):
114        """Show the details of a given item"""
115        item = MediaItem.objects.get(public_id=public_id)
116       
117        formats = []
118        for encoder in self.encoders:
119            formats.append({'name': encoder.format(), 'extension': encoder.file_extension()})
120
121        graphers = []
122        for grapher in self.graphers:
123            graphers.append({'name':grapher.name(), 'id': grapher.id()})
124        if request.REQUEST.has_key('grapher_id'):
125            grapher_id = request.REQUEST['grapher_id']
126        else:
127            grapher_id = 'waveform'
128       
129        analyze_file = public_id + '.xml'
130       
131        if self.cache.exists(analyze_file):
132            analyzers = self.cache.read_analyzer_xml(analyze_file)
133            if not item.approx_duration:
134                for analyzer in analyzers:
135                    if analyzer['id'] == 'duration':
136                        value = analyzer['value']
137                        time = value.split(':')
138                        time[2] = time[2].split('.')[0]
139                        time = ':'.join(time)
140                        item.approx_duration = time
141                        item.save()
142        else:
143            analyzers = []
144            analyzers_sub = []
145            if item.file:
146                decoder  = timeside.decoder.FileDecoder(item.file.path)
147                pipe = decoder
148               
149                for analyzer in self.analyzers:
150                    subpipe = analyzer()
151                    analyzers_sub.append(subpipe)
152                    pipe = pipe | subpipe
153                   
154                pipe.run()
155               
156                mime_type = decoder.format()
157                analyzers.append({'name': 'Mime type', 'id': 'mime_type', 'unit': '', 'value': mime_type})
158                   
159                for analyzer in analyzers_sub:
160                    value = analyzer.result()
161                    if analyzer.id() == 'duration':
162                        approx_value = int(round(value))
163                        item.approx_duration = approx_value
164                        item.save()
165                        value = datetime.timedelta(0,value)
166                       
167                    analyzers.append({'name':analyzer.name(),
168                                      'id':analyzer.id(),
169                                      'unit':analyzer.unit(),
170                                      'value':str(value)})
171                 
172            self.cache.write_analyzer_xml(analyzers, analyze_file)
173           
174       
175        return render(request, template, 
176                    {'item': item, 'export_formats': formats, 
177                    'visualizers': graphers, 'visualizer_id': grapher_id,'analysers': analyzers,  #FIXME analysers
178                    'audio_export_enabled': getattr(settings, 'TELEMETA_DOWNLOAD_ENABLED', True)
179                    })
180
181    def item_analyze(self):
182        pass
183       
184    def item_visualize(self, request, public_id, visualizer_id, width, height):
185        item = MediaItem.objects.get(public_id=public_id)
186        mime_type = 'image/png'
187        grapher_id = visualizer_id
188       
189        for grapher in self.graphers:
190            if grapher.id() == grapher_id:
191                break
192
193        if grapher.id() != grapher_id:
194            raise Http404
195       
196        size = width + '_' + height
197        image_file = '.'.join([public_id, grapher_id, size, 'png'])
198
199        if not self.cache.exists(image_file):
200            if item.file:
201                decoder  = timeside.decoder.FileDecoder(item.file.path)
202                graph = grapher(width = int(width), height = int(height))
203                pipe = decoder | graph
204                pipe.run()
205                graph.render(self.cache.dir + os.sep + image_file)
206               
207        response = HttpResponse(self.cache.read_stream_bin(image_file), mimetype = mime_type)
208        return response
209
210    def list_export_extensions(self):
211        "Return the recognized item export file extensions, as a list"
212        list = []
213        for encoder in self.encoders:
214            list.append(encoder.file_extension())
215        return list
216
217    def item_export(self, request, public_id, extension):                   
218        """Export a given media item in the specified format (OGG, FLAC, ...)"""
219
220        if extension != 'mp3' and not getattr(settings, 'TELEMETA_DOWNLOAD_ENABLED', False):
221            raise Http404 # FIXME: should be some sort of permissions denied error
222
223        for encoder in self.encoders:
224            if encoder.file_extension() == extension:
225                break
226
227        if encoder.file_extension() != extension:
228            raise Http404('Unknown export file extension: %s' % extension)
229
230        mime_type = encoder.mime_type()
231        file = public_id + '.' + encoder.file_extension()
232        item = MediaItem.objects.get(public_id=public_id)
233        audio = item.file.path
234        decoder = timeside.decoder.FileDecoder(audio)
235
236        if decoder.format() == mime_type:
237            # source > stream
238            response = HttpResponse(stream_from_file(audio), mimetype = mime_type)
239           
240        else:       
241            if not self.cache_export.exists(file):
242                # source > encoder > stream
243                media = self.cache_export.dir + os.sep + file
244                proc = encoder(media)
245#                metadata = dublincore.express_item(item).to_list()
246#                enc.set_metadata(metadata)
247                pipe = decoder | proc
248                pipe.run()
249                response = HttpResponse(stream_from_processor(decoder, proc), mimetype = mime_type)
250            else:
251                response = HttpResponse(self.cache_export.read_stream_bin(file), mimetype = mime_type)
252       
253        response['Content-Disposition'] = 'attachment'
254        return response
255
256    def edit_search(self, request, criteria=None):
257        year_min, year_max = MediaCollection.objects.all().recording_year_range()
258        rec_years = year_min and year_max and range(year_min, year_max + 1) or []
259        year_min, year_max = MediaCollection.objects.all().publishing_year_range()
260        pub_years = year_min and year_max and range(year_min, year_max + 1) or []
261        return render(request, 'telemeta/search_criteria.html', {
262            'rec_years': rec_years,
263            'pub_years': pub_years,
264            'ethnic_groups': MediaItem.objects.all().ethnic_groups(),
265            'criteria': criteria
266        })
267
268    def complete_location(self, request, with_items=True):
269        input = request.REQUEST
270       
271        token = input['q']
272        limit = int(input['limit'])
273        if with_items:
274            locations = MediaItem.objects.all().locations()
275        else:
276            locations = Location.objects.all()
277
278        locations = locations.filter(name__istartswith=token).order_by('name')[:limit]
279        data = [unicode(l) + " (%d items)" % l.items().count() for l in locations]
280
281        return HttpResponse("\n".join(data))
282
283    def search(self, request, type = None):
284        """Perform a search through collections and items metadata"""
285        collections = MediaCollection.objects.enriched()
286        items = MediaItem.objects.enriched()
287        input = request.REQUEST
288        criteria = {}
289
290        switch = {
291            'pattern': lambda value: ( 
292                collections.quick_search(value), 
293                items.quick_search(value)),
294            'title': lambda value: (
295                collections.word_search('title', value), 
296                items.by_title(value)),
297            'location': lambda value: (
298                collections.by_location(Location.objects.get(name=value)), 
299                items.by_location(Location.objects.get(name=value))),
300            'continent': lambda value: (
301                collections.by_continent(value), 
302                items.filter(continent = value)),
303            'ethnic_group': lambda value: (
304                collections.by_ethnic_group(value), 
305                items.filter(ethnic_group = value),
306                EthnicGroup.objects.get(pk=value)),
307            'creator': lambda value: (
308                collections.word_search('creator', value),
309                items.word_search('collection__creator', value)),
310            'collector': lambda value: (
311                collections.by_fuzzy_collector(value),
312                items.by_fuzzy_collector(value)),
313            'rec_year_from': lambda value: (
314                collections.by_recording_year(int(value), int(input.get('rec_year_to', value))), 
315                items.by_recording_date(datetime.date(int(value), 1, 1), 
316                                        datetime.date(int(input.get('rec_year_to', value)), 12, 31))),
317            'rec_year_to': lambda value: (collections, items),
318            'pub_year_from': lambda value: (
319                collections.by_publish_year(int(value), int(input.get('pub_year_to', value))), 
320                items.by_publish_year(int(value), int(input.get('pub_year_to', value)))),
321            'pub_year_to': lambda value: (collections, items),
322        }
323       
324        for key, value in input.items():
325            func = switch.get(key)
326            if func and value and value != "0":
327                try:
328                    res = func(value)
329                    if len(res) > 2:
330                        collections, items, value = res
331                    else: 
332                        collections, items = res
333                except ObjectDoesNotExist:
334                    collections = collections.none()
335                    items = items.none()
336
337                criteria[key] = value
338
339        if type is None:
340            if collections.count() and not items.count():
341                type = 'collections'
342            else:
343                type = 'items'
344
345        if type == 'items':
346            objects = items
347        else:
348            objects = collections
349
350        return list_detail.object_list(request, objects, 
351            template_name='telemeta/search_results.html', paginate_by=20,
352            extra_context={'criteria': criteria, 'collections_num': collections.count(), 
353                'items_num': items.count(), 'type' : type})
354
355    def __get_enumerations_list(self):
356        from django.db.models import get_models
357        models = get_models(telemeta.models)
358
359        enumerations = []
360        for model in models:
361            if issubclass(model, Enumeration):
362                enumerations.append({"name": model._meta.verbose_name, 
363                    "id": model._meta.module_name})
364
365        cmp = lambda obj1, obj2: unaccent_icmp(obj1['name'], obj2['name'])
366        enumerations.sort(cmp)
367        return enumerations                   
368   
369    def __get_admin_context_vars(self):
370        return {"enumerations": self.__get_enumerations_list()}
371
372    @login_required
373    def admin_index(self, request):
374        return render(request, 'telemeta/admin.html', self. __get_admin_context_vars())
375
376    def __get_enumeration(self, id):
377        from django.db.models import get_models
378        models = get_models(telemeta.models)
379        for model in models:
380            if model._meta.module_name == id:
381                break
382
383        if model._meta.module_name != id:
384            return None
385
386        return model
387
388    @login_required
389    def edit_enumeration(self, request, enumeration_id):       
390
391        enumeration  = self.__get_enumeration(enumeration_id)
392        if enumeration == None:
393            raise Http404
394
395        vars = self.__get_admin_context_vars()
396        vars["enumeration_id"] = enumeration._meta.module_name
397        vars["enumeration_name"] = enumeration._meta.verbose_name           
398        vars["enumeration_values"] = enumeration.objects.all()
399        return render(request, 'telemeta/enumeration_edit.html', vars)
400
401    @login_required
402    def add_to_enumeration(self, request, enumeration_id):       
403
404        enumeration  = self.__get_enumeration(enumeration_id)
405        if enumeration == None:
406            raise Http404
407
408        enumeration_value = enumeration(value=request.POST['value'])
409        enumeration_value.save()
410
411        return self.edit_enumeration(request, enumeration_id)
412
413    @login_required
414    def update_enumeration(self, request, enumeration_id):       
415       
416        enumeration  = self.__get_enumeration(enumeration_id)
417        if enumeration == None:
418            raise Http404
419
420        if request.POST.has_key("remove"):
421            enumeration.objects.filter(id__in=request.POST.getlist('sel')).delete()
422
423        return self.edit_enumeration(request, enumeration_id)
424
425    @login_required
426    def edit_enumeration_value(self, request, enumeration_id, value_id):       
427
428        enumeration  = self.__get_enumeration(enumeration_id)
429        if enumeration == None:
430            raise Http404
431       
432        vars = self.__get_admin_context_vars()
433        vars["enumeration_id"] = enumeration._meta.module_name
434        vars["enumeration_name"] = enumeration._meta.verbose_name           
435        vars["enumeration_record"] = enumeration.objects.get(id__exact=value_id)
436        return render(request, 'telemeta/enumeration_edit_value.html', vars)
437
438    @login_required
439    def update_enumeration_value(self, request, enumeration_id, value_id):       
440
441        if request.POST.has_key("save"):
442            enumeration  = self.__get_enumeration(enumeration_id)
443            if enumeration == None:
444                raise Http404
445       
446            record = enumeration.objects.get(id__exact=value_id)
447            record.value = request.POST["value"]
448            record.save()
449
450        return self.edit_enumeration(request, enumeration_id)
451 
452    def collection_playlist(self, request, public_id, template, mimetype):
453        try:
454            collection = MediaCollection.objects.get(public_id=public_id)
455        except ObjectDoesNotExist:
456            raise Http404
457
458        template = loader.get_template(template)
459        context = RequestContext(request, {'collection': collection, 'host': request.META['HTTP_HOST']})
460        return HttpResponse(template.render(context), mimetype=mimetype)
461
462    def item_playlist(self, request, public_id, template, mimetype):
463        try:
464            item = MediaItem.objects.get(public_id=public_id)
465        except ObjectDoesNotExist:
466            raise Http404
467
468        template = loader.get_template(template)
469        context = RequestContext(request, {'item': item, 'host': request.META['HTTP_HOST']})
470        return HttpResponse(template.render(context), mimetype=mimetype)
471
472    def list_continents(self, request):
473        continents = MediaItem.objects.all().countries(group_by_continent=True)
474        return render(request, 'telemeta/geo_continents.html', 
475                    {'continents': continents, 'gmap_key': settings.TELEMETA_GMAP_KEY })
476
477    def country_info(self, request, id):
478        country = Location.objects.get(pk=id)
479        return render(request, 'telemeta/country_info.html', {
480            'country': country, 'continent': country.continents()[0]})
481
482    def list_countries(self, request, continent):                   
483        continent = Location.objects.by_flatname(continent)[0]
484        countries = MediaItem.objects.by_location(continent).countries()
485
486        return render(request, 'telemeta/geo_countries.html', {
487            'continent': continent,
488            'countries': countries
489        })
490
491    def list_country_collections(self, request, continent, country):
492        continent = Location.objects.by_flatname(continent)[0]
493        country = Location.objects.by_flatname(country)[0]
494        objects = MediaCollection.objects.enriched().by_location(country)
495        return list_detail.object_list(request, objects, 
496            template_name='telemeta/geo_country_collections.html', paginate_by=20,
497            extra_context={'country': country, 'continent': continent})
498
499    def list_country_items(self, request, continent, country):
500        continent = Location.objects.by_flatname(continent)[0]
501        country = Location.objects.by_flatname(country)[0]
502        objects = MediaItem.objects.enriched().by_location(country)
503        return list_detail.object_list(request, objects, 
504            template_name='telemeta/geo_country_items.html', paginate_by=20,
505            extra_context={'country': country, 'continent': continent})
506
507    def handle_oai_request(self, request):
508        url         = 'http://' + request.META['HTTP_HOST'] + request.path
509        datasource  = TelemetaOAIDataSource()
510        admin       = settings.ADMINS[0][1]
511        provider    = oai.DataProvider(datasource, "Telemeta", url, admin)
512        args        = request.GET.copy()
513        args.update(request.POST)
514        return HttpResponse(provider.handle(args), mimetype='text/xml')
515       
516    def render_flatpage(self, request, path):
517        try:
518            content = pages.get_page_content(request, path)
519        except pages.MalformedPagePath:
520            return redirect(request.path + '/')
521
522        if isinstance(content, pages.PageAttachment):
523            return HttpResponse(content, content.mimetype())
524        else:
525            return render(request, 'telemeta/flatpage.html', {'page_content': content })
526
527    def logout(self, request):
528        auth.logout(request)
529        return redirect('telemeta-home')
Note: See TracBrowser for help on using the repository browser.