source: telemeta/views/base.py @ e12b60a8

cremcrem2devdev2diademsfeature/breadcrumbsfeature/ts-0.5feature/ts-0.5.4feature/writecachegenericinstru_searchlamlam2mapsv3mergenlivemultiproductionrelease/1.4.4sabiodsecurityserversocialstoragetelecastertest
Last change on this file since e12b60a8 was e12b60a8, checked in by yomguy <yomguy@…>, 2 years ago

add all graphers to first analyses, set new settings parameter for default size values

  • Property mode set to 100644
File size: 69.6 KB
Line 
1# -*- coding: utf-8 -*-
2# Copyright (C) 2007-2010 Samalyse SARL
3# Copyright (C) 2010-2012 Parisson SARL
4
5# This software is a computer program whose purpose is to backup, analyse,
6# transcode and stream any audio content with its metadata over a web frontend.
7
8# This software is governed by the CeCILL  license under French law and
9# abiding by the rules of distribution of free software.  You can  use,
10# modify and/ or redistribute the software under the terms of the CeCILL
11# license as circulated by CEA, CNRS and INRIA at the following URL
12# "http://www.cecill.info".
13
14# As a counterpart to the access to the source code and  rights to copy,
15# modify and redistribute granted by the license, users are provided only
16# with a limited warranty  and the software's author,  the holder of the
17# economic rights,  and the successive licensors  have only  limited
18# liability.
19
20# In this respect, the user's attention is drawn to the risks associated
21# with loading,  using,  modifying and/or developing or reproducing the
22# software by the user in light of its specific status of free software,
23# that may mean  that it is complicated to manipulate,  and  that  also
24# therefore means  that it is reserved for developers  and  experienced
25# professionals having in-depth computer knowledge. Users are therefore
26# encouraged to load and test the software's suitability as regards their
27# requirements in conditions enabling the security of their systems and/or
28# data to be ensured and,  more generally, to use and operate it in the
29# same conditions as regards security.
30
31# The fact that you are presently reading this means that you have had
32# knowledge of the CeCILL license and that you accept its terms.
33
34# Authors: Olivier Guilyardi <olivier@samalyse.com>
35#          Guillaume Pellerin <yomguy@parisson.com>
36
37import re
38import os
39import sys
40import csv
41import time
42import random
43import datetime
44import timeside
45
46from jsonrpc import jsonrpc_method
47
48from django.utils.decorators import method_decorator
49from django.contrib.auth import authenticate, login
50from django.template import RequestContext, loader
51from django import template
52from django.http import HttpResponse, HttpResponseRedirect
53from django.http import Http404
54from django.shortcuts import render_to_response, redirect, get_object_or_404
55from django.views.generic import list_detail
56from django.views.generic import DetailView
57from django.conf import settings
58from django.contrib import auth
59from django.contrib import messages
60from django.contrib.auth.decorators import login_required, permission_required
61from django.core.context_processors import csrf
62from django.forms.models import modelformset_factory, inlineformset_factory
63from django.contrib.auth.models import User
64from django.utils.translation import ugettext
65from django.contrib.auth.forms import UserChangeForm
66from django.core.exceptions import ObjectDoesNotExist
67from django.contrib.syndication.views import Feed
68
69from telemeta.models import *
70import telemeta.models
71import telemeta.interop.oai as oai
72from telemeta.interop.oaidatasource import TelemetaOAIDataSource
73from telemeta.util.unaccent import unaccent
74from telemeta.util.unaccent import unaccent_icmp
75from telemeta.util.logger import Logger
76from telemeta.util.unicode import UnicodeWriter
77from telemeta.cache import TelemetaCache
78import telemeta.views.pages as pages
79from telemeta.forms import *
80
81# Model type definition
82mods = {'item': MediaItem, 'collection': MediaCollection,
83        'corpus': MediaCorpus, 'fonds': MediaFonds, 'marker': MediaItemMarker, }
84
85# TOOLS
86
87def render(request, template, data = None, mimetype = None):
88    return render_to_response(template, data, context_instance=RequestContext(request),
89                              mimetype=mimetype)
90
91def stream_from_processor(__decoder, __processor, __flag, metadata=None):
92    while True:
93        __frames, __eodproc = __processor.process(*__decoder.process())
94        if __eodproc or not len(__frames):
95            if metadata:
96                __processor.set_metadata(metadata)
97                __processor.write_metadata()
98            __flag.value = True
99            __flag.save()
100            break
101        yield __processor.chunk
102
103def stream_from_file(__file):
104    chunk_size = 0x100000
105    f = open(__file, 'r')
106    while True:
107        __chunk = f.read(chunk_size)
108        if not len(__chunk):
109            f.close()
110            break
111        yield __chunk
112
113def get_public_access(access, year_from=None, year_to=None):
114    # Rolling publishing date : public access is given when time between recorded year
115    # and current year is over the settings value PUBLIC_ACCESS_PERIOD
116    if year_from and not year_from == 0:
117        year = year_from
118    elif year_to and not year_to == 0:
119        year = year_to
120    else:
121        year = 0
122    if access == 'full':
123        public_access = True
124    else:
125        public_access = False
126        if year and not year == 'None':
127            year_now = datetime.datetime.now().strftime("%Y")
128            if int(year_now) - int(year) >= settings.TELEMETA_PUBLIC_ACCESS_PERIOD:
129                public_access = True
130        else:
131            public_access = False
132    return public_access
133
134def get_revisions(nb, user=None):
135    last_revisions = Revision.objects.order_by('-time')
136    if user:
137        last_revisions = last_revisions.filter(user=user)
138    last_revisions = last_revisions[0:nb]
139    revisions = []
140
141    for revision in last_revisions:
142        for type in mods.keys():
143            if revision.element_type == type:
144                try:
145                    element = mods[type].objects.get(pk=revision.element_id)
146                except:
147                    element = None
148        if not element == None:
149            revisions.append({'revision': revision, 'element': element})
150    return revisions
151
152def get_playlists(request, user=None):
153    if not user:
154        user = request.user
155    playlists = []
156    if user.is_authenticated():
157        user_playlists = Playlist.objects.filter(author=user)
158        for playlist in user_playlists:
159            playlist_resources = PlaylistResource.objects.filter(playlist=playlist)
160            resources = []
161            for resource in playlist_resources:
162                try:
163                    for type in mods.keys():
164                        if resource.resource_type == type:
165                            element = mods[type].objects.get(id=resource.resource_id)
166                except:
167                    element = None
168                resources.append({'element': element, 'type': resource.resource_type, 'public_id': resource.public_id })
169            playlists.append({'playlist': playlist, 'resources': resources})
170    return playlists
171
172def check_related_media(medias):
173    for media in medias:
174        if not media.mime_type:
175            media.set_mime_type()
176            media.save()
177        if not media.title and media.url:
178            if 'https' in media.url:
179                media.url = media.url.replace('https', 'http')
180            import lxml.etree
181            parser = lxml.etree.HTMLParser()
182            tree = lxml.etree.parse(media.url, parser)
183            try:
184                title = tree.find(".//title").text
185            except:
186                title = media.url
187            media.title = title.replace('\n', '').strip()
188            media.save()
189
190def auto_code(resources, base_code):
191    index = 1
192    while True:
193        code = base_code + '_' + str(index)
194        r = resources.filter(code=code)
195        if not r:
196            break
197        index += 1
198    return code
199
200
201class GeneralView(object):
202    """Provide general web UI methods"""
203
204    def home(self, request):
205        """Render the index page"""
206
207        template = loader.get_template('telemeta/home.html')
208
209        sound_items = MediaItem.objects.sound()
210        _sound_pub_items = []
211        for item in sound_items:
212            if get_public_access(item.public_access,  str(item.recorded_from_date).split('-')[0],
213                                            str(item.recorded_to_date).split('-')[0]):
214                _sound_pub_items.append(item)
215
216        random.shuffle(_sound_pub_items)
217        if len(_sound_pub_items) != 0:
218            sound_pub_item = _sound_pub_items[0]
219        else:
220            sound_pub_item = None
221        if len(_sound_pub_items) == 2:
222            sound_pub_items = [_sound_pub_items[1]]
223        elif len(_sound_pub_items) > 2:
224            sound_pub_items = _sound_pub_items[1:3]
225        else:
226            sound_pub_items = None
227
228        revisions = get_revisions(4)
229        context = RequestContext(request, {
230                    'page_content': pages.get_page_content(request, 'home', ignore_slash_issue=True),
231                    'revisions': revisions,  'sound_pub_items': sound_pub_items,
232                    'sound_pub_item': sound_pub_item })
233        return HttpResponse(template.render(context))
234
235    def lists(self, request):
236        """Render the home page"""
237
238        if request.user.is_authenticated():
239            template='telemeta/lists.html'
240            playlists = get_playlists(request)
241            revisions = get_revisions(100)
242            searches = Search.objects.filter(username=request.user)
243            user_revisions = get_revisions(25, request.user)
244            return render(request, template, {'playlists': playlists, 'searches': searches,
245                                              'revisions': revisions, 'user_revisions': user_revisions })
246        else:
247            template = 'telemeta/messages.html'
248            mess = ugettext('Access not allowed')
249            title = ugettext('Lists') + ' : ' + mess
250            description = ugettext('Please login or contact the website administator to get a private access.')
251            messages.error(request, title)
252            return render(request, template, {'description' : description})
253
254    def edit_search(self, request, criteria=None):
255        year_min, year_max = MediaCollection.objects.all().recording_year_range()
256        rec_years = year_min and year_max and range(year_min, year_max + 1) or []
257        year_min, year_max = MediaCollection.objects.all().publishing_year_range()
258        pub_years = year_min and year_max and range(year_min, year_max + 1) or []
259        searches = Search.objects.filter(username=request.user)
260        return render(request, 'telemeta/search_criteria.html', {
261            'rec_years': rec_years,
262            'pub_years': pub_years,
263            'ethnic_groups': MediaItem.objects.all().ethnic_groups(),
264            'criteria': criteria,
265            'searches': searches,
266        })
267
268    def handle_oai_request(self, request):
269        host = request.META['HTTP_HOST']
270        datasource  = TelemetaOAIDataSource()
271        repository_name = settings.TELEMETA_ORGANIZATION
272        url         = 'http://' + host + request.path
273        admin       = settings.ADMINS[0][1]
274        provider    = oai.DataProvider(datasource, repository_name, url, admin)
275        args        = request.GET.copy()
276        args.update(request.POST)
277        return HttpResponse(provider.handle(args), mimetype='text/xml')
278
279    def render_flatpage(self, request, path):
280        try:
281            content = pages.get_page_content(request, path)
282        except pages.MalformedPagePath:
283            return redirect(request.path + '/')
284
285        if isinstance(content, pages.PageAttachment):
286            return HttpResponse(content, content.mimetype())
287        else:
288            return render(request, 'telemeta/flatpage.html', {'page_content': content })
289
290    def logout(self, request):
291        auth.logout(request)
292        return redirect('telemeta-home')
293
294    def search(self, request, type = None):
295        """Perform a search through collections and items metadata"""
296        collections = MediaCollection.objects.enriched()
297        items = MediaItem.objects.enriched()
298        corpus = MediaCorpus.objects.all()
299        fonds  = MediaFonds.objects.all()
300        input = request.REQUEST
301        criteria = {}
302
303        switch = {
304            'pattern': lambda value: (
305                collections.quick_search(value),
306                items.quick_search(value),
307                corpus.quick_search(value),
308                fonds.quick_search(value),
309                ),
310            'title': lambda value: (
311                collections.word_search('title', value),
312                items.by_title(value),
313                corpus.word_search('title', value),
314                fonds.word_search('title', value)),
315            'location': lambda value: (
316                collections.by_location(Location.objects.get(name=value)),
317                items.by_location(Location.objects.get(name=value))),
318            'continent': lambda value: (
319                collections.by_continent(value),
320                items.filter(continent = value)),
321            'ethnic_group': lambda value: (
322                collections.by_ethnic_group(value),
323                items.filter(ethnic_group = value),
324                EthnicGroup.objects.get(pk=value)),
325            'creator': lambda value: (
326                collections.word_search('creator', value),
327                items.word_search('collection__creator', value)),
328            'collector': lambda value: (
329                collections.by_fuzzy_collector(value),
330                items.by_fuzzy_collector(value)),
331            'rec_year_from': lambda value: (
332                collections.by_recording_year(int(value), int(input.get('rec_year_to', value))),
333                items.by_recording_date(datetime.date(int(value), 1, 1),
334                                        datetime.date(int(input.get('rec_year_to', value)), 12, 31))),
335            'rec_year_to': lambda value: (collections, items),
336            'pub_year_from': lambda value: (
337                collections.by_publish_year(int(value), int(input.get('pub_year_to', value))),
338                items.by_publish_year(int(value), int(input.get('pub_year_to', value)))),
339            'pub_year_to': lambda value: (collections, items),
340            'sound': lambda value: (
341                collections.sound(),
342                items.sound()),
343        }
344
345        for key, value in input.items():
346            func = switch.get(key)
347            if func and value and value != "0":
348                try:
349                    res = func(value)
350                    if len(res)  > 4:
351                        collections, items, corpus, fonds, value = res
352                    elif len(res) == 4:
353                        collections, items, corpus, fonds = res
354                    elif len(res) == 3:
355                        collections, items, value = res
356                        corpus = corpus.none()
357                        fonds = fonds.none()
358                    else:
359                        collections, items = res
360                        corpus = corpus.none()
361                        fonds = fonds.none()
362
363                except ObjectDoesNotExist:
364                    collections = collections.none()
365                    items = items.none()
366                    corpus = corpus.none()
367                    fonds = fonds.none()
368
369                criteria[key] = value
370
371        # Save the search
372        user = request.user
373        if user:
374            if user.is_authenticated():
375                search = Search(username=user)
376                search.save()
377                if criteria:
378                    for key in criteria.keys():
379                        value = criteria[key]
380                        if key == 'ethnic_group':
381                            try:
382                                group = EthnicGroup.objects.get(value=value)
383                                value = group.id
384                            except:
385                                value = ''
386                        criter = Criteria(key=key, value=value)
387                        criter.save()
388                        search.criteria.add(criter)
389                    search.save()
390
391        if type is None:
392            if collections.count():
393                type = 'collections'
394            else:
395                type = 'items'
396
397        if type == 'items':
398            objects = items
399        elif type == 'collections':
400            objects = collections
401        elif type == 'corpus':
402            objects = corpus
403        elif type == 'fonds':
404            objects = fonds
405
406        return list_detail.object_list(request, objects,
407            template_name='telemeta/search_results.html', paginate_by=20,
408            extra_context={'criteria': criteria, 'collections_num': collections.count(),
409                'items_num': items.count(), 'corpus_num': corpus.count(), 'fonds_num': fonds.count(),
410                'type' : type,})
411
412    def complete_location(self, request, with_items=True):
413        input = request.REQUEST
414
415        token = input['q']
416        limit = int(input['limit'])
417        if with_items:
418            locations = MediaItem.objects.all().locations()
419        else:
420            locations = Location.objects.all()
421
422        locations = locations.filter(name__istartswith=token).order_by('name')[:limit]
423        data = [unicode(l) + " (%d items)" % l.items().count() for l in locations]
424
425        return HttpResponse("\n".join(data))
426
427    @method_decorator(login_required)
428    def users(self, request):
429        users = User.objects.all()
430        return render(request, 'telemeta/users.html', {'users': users})
431
432class CollectionView(object):
433    """Provide Collections web UI methods"""
434
435    def collection_detail(self, request, public_id, template='telemeta/collection_detail.html'):
436        collection = MediaCollection.objects.get(public_id=public_id)
437        items = collection.items.enriched()
438        items = items.order_by('code', 'old_code')
439
440        if collection.public_access == 'none' and not (request.user.is_staff or request.user.is_superuser):
441            mess = ugettext('Access not allowed')
442            title = ugettext('Collection') + ' : ' + public_id + ' : ' + mess
443            description = ugettext('Please login or contact the website administator to get a private access.')
444            messages.error(request, title)
445            return render(request, 'telemeta/messages.html', {'description' : description})
446
447        public_access = get_public_access(collection.public_access, collection.recorded_from_year,
448                                                collection.recorded_to_year)
449        playlists = get_playlists(request)
450
451        related_media = MediaCollectionRelated.objects.filter(collection=collection)
452        check_related_media(related_media)
453        parents = MediaCorpus.objects.filter(children=collection)
454        revisions = Revision.objects.filter(element_type='collection',
455                                            element_id=collection.id).order_by('-time')
456        if revisions:
457            last_revision = revisions[0]
458        else:
459            last_revision = None
460
461        return render(request, template, {'collection': collection, 'playlists': playlists,
462                'public_access': public_access, 'items': items, 'related_media': related_media,
463                'parents': parents, 'last_revision': last_revision })
464
465    @method_decorator(permission_required('telemeta.change_mediacollection'))
466    def collection_edit(self, request, public_id, template='telemeta/collection_edit.html'):
467        collection = MediaCollection.objects.get(public_id=public_id)
468        if request.method == 'POST':
469            form = MediaCollectionForm(data=request.POST, files=request.FILES, instance=collection)
470            if form.is_valid():
471                code = form.cleaned_data['code']
472                if not code:
473                    code = public_id
474                form.save()
475                collection.set_revision(request.user)
476                return HttpResponseRedirect('/archives/collections/'+code)
477        else:
478            form = MediaCollectionForm(instance=collection)
479
480        return render(request, template, {'collection': collection, "form": form,})
481
482    @method_decorator(permission_required('telemeta.add_mediacollection'))
483    def collection_add(self, request, template='telemeta/collection_add.html'):
484        collection = MediaCollection()
485        if request.method == 'POST':
486            form = MediaCollectionForm(data=request.POST, files=request.FILES, instance=collection)
487            if form.is_valid():
488                code = form.cleaned_data['code']
489                if not code:
490                    code = public_id
491                form.save()
492                collection.set_revision(request.user)
493                return HttpResponseRedirect('/archives/collections/'+code)
494        else:
495            form = MediaCollectionForm(instance=collection)
496
497        return render(request, template, {'collection': collection, "form": form,})
498
499    @method_decorator(permission_required('telemeta.add_mediacollection'))
500    def collection_copy(self, request, public_id, template='telemeta/collection_edit.html'):
501        if request.method == 'POST':
502            collection = MediaCollection()
503            form = MediaCollectionForm(data=request.POST, files=request.FILES, instance=collection)
504            if form.is_valid():
505                code = form.cleaned_data['code']
506                if not code:
507                    code = public_id
508                form.save()
509                collection.set_revision(request.user)
510                return HttpResponseRedirect('/archives/collections/'+code)
511        else:
512            collection = MediaCollection.objects.get(public_id=public_id)
513            form = MediaCollectionForm(instance=collection)
514
515        return render(request, template, {'collection': collection, "form": form,})
516
517    def collection_playlist(self, request, public_id, template, mimetype):
518        try:
519            collection = MediaCollection.objects.get(public_id=public_id)
520        except ObjectDoesNotExist:
521            raise Http404
522
523        template = loader.get_template(template)
524        context = RequestContext(request, {'collection': collection, 'host': request.META['HTTP_HOST']})
525        return HttpResponse(template.render(context), mimetype=mimetype)
526
527    @method_decorator(permission_required('telemeta.delete_mediacollection'))
528    def collection_delete(self, request, public_id):
529        """Delete a given collection"""
530        collection = MediaCollection.objects.get(public_id=public_id)
531        collection.delete()
532        return HttpResponseRedirect('/archives/collections/')
533
534    def related_media_collection_stream(self, request, collection_public_id, media_id):
535        collection = MediaCollection.objects.get(public_id=collection_public_id)
536        media = MediaCollectionRelated.objects.get(collection=collection, id=media_id)
537        response = HttpResponse(stream_from_file(media.file.path), mimetype=media.mime_type)
538#        response['Content-Disposition'] = 'attachment'
539        return response
540
541    @method_decorator(permission_required('telemeta.change_mediacollection'))
542    def related_media_edit(self, request, public_id, template):
543        collection = MediaCollection.objects.get(public_id=public_id)
544        MediaCollectionRelatedFormSet = inlineformset_factory(MediaCollection, MediaCollectionRelated, form=MediaCollectionRelatedForm)
545        if request.method == 'POST':
546            formset = MediaCollectionRelatedFormSet(data=request.POST, files=request.FILES, instance=collection)
547            if formset.is_valid():
548                formset.save()
549                collection.set_revision(request.user)
550                return HttpResponseRedirect('/archives/collections/'+public_id)
551        else:
552            formset = MediaCollectionRelatedFormSet(instance=collection)
553
554        return render(request, template, {'collection': collection, 'formset': formset,})
555
556class ItemView(object):
557    """Provide Collections web UI methods"""
558
559    graphers = timeside.core.processors(timeside.api.IGrapher)
560    decoders = timeside.core.processors(timeside.api.IDecoder)
561    encoders = timeside.core.processors(timeside.api.IEncoder)
562    analyzers = timeside.core.processors(timeside.api.IAnalyzer)
563    cache_data = TelemetaCache(settings.TELEMETA_DATA_CACHE_DIR)
564    cache_export = TelemetaCache(settings.TELEMETA_EXPORT_CACHE_DIR)
565
566    def item_previous_next(self, item):
567        # Get previous and next items
568        pks = []
569        items = MediaItem.objects.filter(collection=item.collection)
570        items = items.order_by('code', 'old_code')
571
572        if len(items) > 1:
573            for it in items:
574                pks.append(it.pk)
575            for pk in pks:
576                if pk == item.pk:
577                    if pk == pks[0]:
578                        previous_pk = pks[-1]
579                        next_pk = pks[1]
580                    elif pk == pks[-1]:
581                        previous_pk = pks[-2]
582                        next_pk = pks[0]
583                    else:
584                        previous_pk = pks[pks.index(pk)-1]
585                        next_pk = pks[pks.index(pk)+1]
586                    for it in items:
587                        if it.pk == previous_pk:
588                            previous = it
589                        if it.pk == next_pk:
590                            next = it
591                    previous = previous.public_id
592                    next = next.public_id
593        else:
594             previous = item.public_id
595             next = item.public_id
596
597        return previous, next
598
599    def item_detail(self, request, public_id=None, marker_id=None, width=None, height=None,
600                        template='telemeta/mediaitem_detail.html'):
601        """Show the details of a given item"""
602
603        if not public_id and marker_id:
604            marker = MediaItemMarker.objects.get(public_id=marker_id)
605            item_id = marker.item_id
606            item = MediaItem.objects.get(id=item_id)
607        else:
608            item = MediaItem.objects.get(public_id=public_id)
609
610        item_public_access = item.public_access != 'none' or item.collection.public_access != 'none'
611        if not item_public_access and not (request.user.is_staff or request.user.is_superuser):
612            mess = ugettext('Access not allowed')
613            title = ugettext('Item') + ' : ' + public_id + ' : ' + mess
614            description = ugettext('Please login or contact the website administator to get a private access.')
615            messages.error(request, title)
616            return render(request, 'telemeta/messages.html', {'description' : description})
617
618        # Get TimeSide processors
619        formats = []
620        for encoder in self.encoders:
621            if settings.TELEMETA_DOWNLOAD_FORMATS:
622                if encoder.file_extension() in settings.TELEMETA_DOWNLOAD_FORMATS:
623                    formats.append({'name': encoder.format(), 'extension': encoder.file_extension()})
624            else:
625                formats.append({'name': encoder.format(), 'extension': encoder.file_extension()})
626
627        graphers = []
628        for grapher in self.graphers:
629            graphers.append({'name':grapher.name(), 'id': grapher.id()})
630        if request.REQUEST.has_key('grapher_id'):
631            grapher_id = request.REQUEST['grapher_id']
632        else:
633            try:
634                grapher_id = settings.TELEMETA_DEFAULT_GRAPHER_ID
635            except:
636                grapher_id = 'waveform'
637
638        previous, next = self.item_previous_next(item)
639        mime_type = self.item_analyze(item)
640        #FIXME: use mimetypes.guess_type
641        if 'quicktime' in mime_type:
642            mime_type = 'video/mp4'
643
644        playlists = get_playlists(request)
645        public_access = get_public_access(item.public_access, str(item.recorded_from_date).split('-')[0],
646                                                str(item.recorded_to_date).split('-')[0])
647
648        related_media = MediaItemRelated.objects.filter(item=item)
649        check_related_media(related_media)
650        revisions = Revision.objects.filter(element_type='item', element_id=item.id).order_by('-time')
651        if revisions:
652            last_revision = revisions[0]
653        else:
654            last_revision = None
655
656        physical_format = Format.objects.filter(item=item)
657        if physical_format:
658            physical_format = physical_format[0]
659
660        return render(request, template,
661                    {'item': item, 'export_formats': formats,
662                    'visualizers': graphers, 'visualizer_id': grapher_id,
663                    'audio_export_enabled': getattr(settings, 'TELEMETA_DOWNLOAD_ENABLED', True),
664                    'previous' : previous, 'next' : next, 'marker': marker_id, 'playlists' : playlists,
665                    'public_access': public_access, 'width': width, 'height': height,
666                    'related_media': related_media, 'mime_type': mime_type, 'last_revision': last_revision,
667                    'physical_format': physical_format,
668                    })
669
670    @method_decorator(permission_required('telemeta.change_mediaitem'))
671    def item_edit(self, request, public_id, template='telemeta/mediaitem_edit.html'):
672        """Edit a given item"""
673        item = MediaItem.objects.get(public_id=public_id)
674
675        formats = []
676        for encoder in self.encoders:
677            #FIXME: timeside cannot encode to FLAC and OGG now :'(
678            if encoder.file_extension() != 'ogg' and encoder.file_extension() != 'flac':
679                formats.append({'name': encoder.format(), 'extension': encoder.file_extension()})
680
681        graphers = []
682        for grapher in self.graphers:
683            graphers.append({'name':grapher.name(), 'id': grapher.id()})
684        if request.REQUEST.has_key('grapher_id'):
685            grapher_id = request.REQUEST['grapher_id']
686        else:
687            try:
688                grapher_id = settings.TELEMETA_DEFAULT_GRAPHER_ID
689            except:
690                grapher_id = 'waveform'
691
692        previous, next = self.item_previous_next(item)
693        mime_type = self.item_analyze(item)
694        #FIXME: use mimetypes.guess_type
695        if 'quicktime' in mime_type:
696            mime_type = 'video/mp4'
697
698        if request.method == 'POST':
699            form = MediaItemForm(data=request.POST, files=request.FILES, instance=item)
700            if form.is_valid():
701                form.save()
702                code = form.cleaned_data['code']
703                if not code:
704                    code = str(item.id)
705                if form.files:
706                    self.cache_data.delete_item_data(code)
707                    self.cache_export.delete_item_data(code)
708                    flags = MediaItemTranscodingFlag.objects.filter(item=item)
709                    analyses = MediaItemAnalysis.objects.filter(item=item)
710                    for flag in flags:
711                        flag.delete()
712                    for analysis in analyses:
713                        analysis.delete()
714                item.set_revision(request.user)
715                return HttpResponseRedirect('/archives/items/'+code)
716        else:
717            form = MediaItemForm(instance=item)
718
719        return render(request, template,
720                    {'item': item, 'export_formats': formats,
721                    'visualizers': graphers, 'visualizer_id': grapher_id,
722                    'audio_export_enabled': getattr(settings, 'TELEMETA_DOWNLOAD_ENABLED', True), "form": form,
723                    'previous' : previous, 'next' : next, 'mime_type': mime_type,
724                    })
725
726    def related_media_item_stream(self, request, item_public_id, media_id):
727        item = MediaItem.objects.get(public_id=item_public_id)
728        media = MediaItemRelated.objects.get(item=item, id=media_id)
729        response = HttpResponse(stream_from_file(media.file.path), mimetype=media.mime_type)
730#        response['Content-Disposition'] = 'attachment; '+'filename='+media.title+'.'+ext
731        return response
732
733    @method_decorator(permission_required('telemeta.change_mediaitem'))
734    def related_media_edit(self, request, public_id, template):
735        item = MediaItem.objects.get(public_id=public_id)
736        MediaItemRelatedFormSet = inlineformset_factory(MediaItem, MediaItemRelated, form=MediaItemRelatedForm)
737        if request.method == 'POST':
738            formset = MediaItemRelatedFormSet(data=request.POST, files=request.FILES, instance=item)
739            if formset.is_valid():
740                formset.save()
741                item.set_revision(request.user)
742                return HttpResponseRedirect('/archives/items/'+public_id)
743        else:
744            formset = MediaItemRelatedFormSet(instance=item)
745
746        return render(request, template, {'item': item, 'formset': formset,})
747
748    @method_decorator(permission_required('telemeta.add_mediaitem'))
749    def item_add(self, request, public_id=None, template='telemeta/mediaitem_add.html'):
750        """Add an item"""
751        if public_id:
752            collection = MediaCollection.objects.get(public_id=public_id)
753            items = MediaItem.objects.filter(collection=collection)
754            code = auto_code(items, collection.code)
755            item = MediaItem(collection=collection, code=code)
756        else:
757            item = MediaItem()
758        if request.method == 'POST':
759            form = MediaItemForm(data=request.POST, files=request.FILES, instance=item)
760            if form.is_valid():
761                form.save()
762                item.set_revision(request.user)
763                code = form.cleaned_data['code']
764                if not code:
765                    code = str(item.id)
766                return HttpResponseRedirect('/archives/items/'+code)
767        else:
768            form = MediaItemForm(instance=item)
769
770
771        return render(request, template, {'item': item, 'form': form})
772
773    @method_decorator(permission_required('telemeta.add_mediaitem'))
774    def item_copy(self, request, public_id, template='telemeta/mediaitem_copy.html'):
775        """Copy a given item"""
776        if request.method == 'POST':
777            source_item = MediaItem.objects.get(public_id=public_id)
778            item = MediaItem()
779            form = MediaItemForm(data=request.POST, files=request.FILES, instance=item)
780            if form.is_valid():
781                form.save()
782                code = form.cleaned_data['code']
783                if not code:
784                    code = str(item.id)
785
786                performances = MediaItemPerformance.objects.filter(media_item=source_item)
787                for performance in performances:
788                    performance.pk = None
789                    performance.id = None
790                    performance.media_item = item
791                    performance.save()
792
793                keywords = MediaItemKeyword.objects.filter(item=source_item)
794                for keyword in keywords:
795                    keyword.pk = None
796                    keyword.id = None
797                    keyword.item = item
798                    keyword.save()
799
800                item.set_revision(request.user)
801                return HttpResponseRedirect('/archives/items/'+code)
802        else:
803            item = MediaItem.objects.get(public_id=public_id)
804            items = MediaItem.objects.filter(collection=item.collection)
805            item.code = auto_code(items, item.collection.code)
806            item.approx_duration = ''
807            form = MediaItemForm(instance=item)
808            form.code = item.code
809            form.file = None
810
811        return render(request, template, {'item': item, "form": form})
812
813    @method_decorator(permission_required('telemeta.delete_mediaitem'))
814    def item_delete(self, request, public_id):
815        """Delete a given item"""
816        item = MediaItem.objects.get(public_id=public_id)
817        collection = item.collection
818        item.delete()
819        return HttpResponseRedirect('/archives/collections/'+collection.code)
820
821    def item_analyze(self, item):
822        analyses = MediaItemAnalysis.objects.filter(item=item)
823        mime_type = ''
824
825        if analyses:
826            for analysis in analyses:
827                if not item.approx_duration and analysis.analyzer_id == 'duration':
828                    value = analysis.value
829                    time = value.split(':')
830                    time[2] = time[2].split('.')[0]
831                    time = ':'.join(time)
832                    item.approx_duration = time
833                    item.save()
834                if analysis.analyzer_id == 'mime_type':
835                    mime_type = analysis.value
836        else:
837            analyzers = []
838            analyzers_sub = []
839            graphers_sub = []
840
841            if item.file:
842                decoder  = timeside.decoder.FileDecoder(item.file.path)
843                pipe = decoder
844
845                for analyzer in self.analyzers:
846                    subpipe = analyzer()
847                    analyzers_sub.append(subpipe)
848                    pipe = pipe | subpipe
849
850                try:
851                    sizes = settings.TELEMETA_DEFAULT_GRAPHER_SIZES
852                except:
853                    sizes = ['360x130', ]
854
855                for grapher in self.graphers:
856                    for size in sizes:
857                        width = size.split('x')[0]
858                        height = size.split('x')[1]
859                        image_file = '.'.join([item.public_id, grapher.id(), size.replace('x', '_'), 'png'])
860                        path = self.cache_data.dir + os.sep + image_file
861                        graph = grapher(width = int(width), height = int(height))
862                        graphers_sub.append({'graph' : graph, 'path': path})
863                        pipe = pipe | graph
864
865                pipe.run()
866
867                for grapher in graphers_sub:
868                    grapher['graph'].watermark('timeside', opacity=.6, margin=(5,5))
869                    f = open(grapher['path'], 'w')
870                    grapher['graph'].render(grapher['path'])
871                    f.close()
872
873                mime_type = decoder.format()
874                analysis = MediaItemAnalysis(item=item, name='MIME type',
875                                             analyzer_id='mime_type', unit='', value=mime_type)
876                analysis.save()
877                analysis = MediaItemAnalysis(item=item, name='Channels',
878                                             analyzer_id='channels',
879                                             unit='', value=decoder.channels())
880                analysis.save()
881                analysis = MediaItemAnalysis(item=item, name='Samplerate',
882                                             analyzer_id='samplerate', unit='Hz',
883                                             value=unicode(decoder.audiorate))
884                analysis.save()
885                analysis = MediaItemAnalysis(item=item, name='Resolution',
886                                             analyzer_id='resolution', unit='bits',
887                                             value=unicode(decoder.audiowidth))
888                analysis.save()
889                analysis = MediaItemAnalysis(item=item, name='Duration',
890                                             analyzer_id='duration', unit='s',
891                                             value=unicode(datetime.timedelta(0,decoder.duration)))
892                analysis.save()
893
894                for analyzer in analyzers_sub:
895                    value = analyzer.result()
896                    analysis = MediaItemAnalysis(item=item, name=analyzer.name(),
897                                                 analyzer_id=analyzer.id(),
898                                                 unit=analyzer.unit(), value=str(value))
899                    analysis.save()
900
901#                FIXME: parse tags on first load
902#                tags = decoder.tags
903
904        return mime_type
905
906    def item_analyze_xml(self, request, public_id):
907        item = MediaItem.objects.get(public_id=public_id)
908        analyses = MediaItemAnalysis.objects.filter(item=item)
909        analyzers = []
910        for analysis in analyses:
911            analyzers.append(analysis.to_dict())
912        mime_type = 'text/xml'
913        response = HttpResponse(self.cache_data.get_analyzer_xml(analyzers), mimetype=mime_type)
914        response['Content-Disposition'] = 'attachment; filename='+public_id+'.xml'
915        return response
916
917    def item_visualize(self, request, public_id, visualizer_id, width, height):
918        item = MediaItem.objects.get(public_id=public_id)
919        mime_type = 'image/png'
920        grapher_id = visualizer_id
921
922        for grapher in self.graphers:
923            if grapher.id() == grapher_id:
924                break
925
926        if grapher.id() != grapher_id:
927            raise Http404
928
929        size = width + '_' + height
930        image_file = '.'.join([public_id, grapher_id, size, 'png'])
931
932        if not self.cache_data.exists(image_file):
933            if item.file:
934                path = self.cache_data.dir + os.sep + image_file
935                decoder  = timeside.decoder.FileDecoder(item.file.path)
936                graph = grapher(width = int(width), height = int(height))
937                pipe = decoder | graph
938                pipe.run()
939                graph.watermark('timeside', opacity=.6, margin=(5,5))
940                f = open(path, 'w')
941                graph.render(path)
942                f.close()
943
944        response = HttpResponse(self.cache_data.read_stream_bin(image_file), mimetype=mime_type)
945        return response
946
947    def list_export_extensions(self):
948        "Return the recognized item export file extensions, as a list"
949        list = []
950        for encoder in self.encoders:
951            list.append(encoder.file_extension())
952        #FIXME: MP4
953        list.append('mp4')
954        return list
955
956    def item_export(self, request, public_id, extension):
957        """Export a given media item in the specified format (OGG, FLAC, ...)"""
958
959        item = MediaItem.objects.get(public_id=public_id)
960        public_access = get_public_access(item.public_access,
961                                          str(item.recorded_from_date).split('-')[0],
962                                          str(item.recorded_to_date).split('-')[0])
963
964        if (not public_access or not extension in settings.TELEMETA_STREAMING_FORMATS) and \
965                    not (request.user.has_perm('telemeta.can_play_all_items') or request.user.is_superuser):
966            mess = ugettext('Access not allowed')
967            title = 'Item file : ' + public_id + '.' + extension + ' : ' + mess
968            description = ugettext('Please login or contact the website administator to get a private access.')
969            messages.error(request, title)
970            return render(request, 'telemeta/messages.html', {'description' : description})
971
972        #FIXME: MP4 handling in TimeSide
973        if 'mp4' in extension:
974            mime_type = 'video/mp4'
975            video = item.file.path
976            response = HttpResponse(stream_from_file(video), mimetype = mime_type)
977            response['Content-Disposition'] = 'attachment'
978            return response
979
980        for encoder in self.encoders:
981            if encoder.file_extension() == extension:
982                break
983
984        if encoder.file_extension() != extension:
985            raise Http404('Unknown export file extension: %s' % extension)
986
987        mime_type = encoder.mime_type()
988        file = public_id + '.' + encoder.file_extension()
989        audio = item.file.path
990
991        flag = MediaItemTranscodingFlag.objects.filter(item=item, mime_type=mime_type)
992        if not flag:
993            flag = MediaItemTranscodingFlag(item=item, mime_type=mime_type)
994            flag.value = False
995            flag.save()
996        else:
997            flag = flag[0]
998
999        format = self.item_analyze(item)
1000        dc_metadata = dublincore.express_item(item).to_list()
1001        mapping = DublinCoreToFormatMetadata(extension)
1002        metadata = mapping.get_metadata(dc_metadata)
1003
1004        if mime_type in format:
1005            # source > stream
1006            if not extension in mapping.unavailable_extensions:
1007                proc = encoder(audio)
1008                proc.set_metadata(metadata)
1009                try:
1010                    proc.write_metadata()
1011                except:
1012                    pass
1013            response = HttpResponse(stream_from_file(audio), mimetype = mime_type)
1014#            fsock = open(audio, 'r')
1015#            response = HttpResponse(fsock, mimetype = mime_type)
1016        else:
1017            media = self.cache_export.dir + os.sep + file
1018            if not self.cache_export.exists(file) or not flag.value:
1019                # source > encoder > stream
1020                decoder = timeside.decoder.FileDecoder(audio)
1021                decoder.setup()
1022                proc = encoder(media, streaming=True)
1023                proc.setup(channels=decoder.channels(), samplerate=decoder.samplerate())
1024                if extension in mapping.unavailable_extensions:
1025                    metadata=None
1026                response = HttpResponse(stream_from_processor(decoder, proc, flag, metadata=metadata), mimetype = mime_type)
1027            else:
1028                # cache > stream
1029                response = HttpResponse(self.cache_export.read_stream_bin(file), mimetype = mime_type)
1030
1031        response['Content-Disposition'] = 'attachment'
1032        return response
1033
1034    def item_playlist(self, request, public_id, template, mimetype):
1035        try:
1036            item = MediaItem.objects.get(public_id=public_id)
1037        except ObjectDoesNotExist:
1038            raise Http404
1039
1040        template = loader.get_template(template)
1041        context = RequestContext(request, {'item': item, 'host': request.META['HTTP_HOST']})
1042        return HttpResponse(template.render(context), mimetype=mimetype)
1043
1044    @method_decorator(permission_required('telemeta.change_mediaitem'))
1045    def item_performances_edit(self, request, public_id, template):
1046        item = MediaItem.objects.get(public_id=public_id)
1047        PerformanceFormSet = inlineformset_factory(MediaItem, MediaItemPerformance, form=MediaItemPerformanceForm)
1048        if request.method == 'POST':
1049            formset = PerformanceFormSet(data=request.POST, instance=item)
1050            if formset.is_valid():
1051                formset.save()
1052                return HttpResponseRedirect('/archives/items/'+public_id)
1053        else:
1054            formset = PerformanceFormSet(instance=item)
1055        return render(request, template, {'item': item, 'formset': formset,})
1056
1057    @method_decorator(permission_required('telemeta.change_mediaitem'))
1058    def item_keywords_edit(self, request, public_id, template):
1059        item = MediaItem.objects.get(public_id=public_id)
1060        FormSet = inlineformset_factory(MediaItem, MediaItemKeyword)
1061        if request.method == 'POST':
1062            formset = FormSet(data=request.POST, instance=item)
1063            if formset.is_valid():
1064                formset.save()
1065                return HttpResponseRedirect('/archives/items/'+public_id)
1066        else:
1067            formset = FormSet(instance=item)
1068        return render(request, template, {'item': item, 'formset': formset,})
1069
1070
1071class AdminView(object):
1072    """Provide Admin web UI methods"""
1073
1074    @method_decorator(permission_required('is_superuser'))
1075    def admin_index(self, request):
1076        return render(request, 'telemeta/admin.html', self.__get_admin_context_vars())
1077
1078    @method_decorator(permission_required('is_superuser'))
1079    def admin_general(self, request):
1080        return render(request, 'telemeta/admin_general.html', self.__get_admin_context_vars())
1081
1082    @method_decorator(permission_required('is_superuser'))
1083    def admin_enumerations(self, request):
1084        return render(request, 'telemeta/admin_enumerations.html', self.__get_admin_context_vars())
1085
1086    @method_decorator(permission_required('is_superuser'))
1087    def admin_users(self, request):
1088        users = User.objects.all()
1089        return render(request, 'telemeta/admin_users.html', {'users': users})
1090
1091    def __get_enumerations_list(self):
1092        from django.db.models import get_models
1093        models = get_models(telemeta.models)
1094
1095        enumerations = []
1096        for model in models:
1097            if issubclass(model, Enumeration):
1098                enumerations.append({"name": model._meta.verbose_name,
1099                    "id": model._meta.module_name})
1100
1101        cmp = lambda obj1, obj2: unaccent_icmp(obj1['name'], obj2['name'])
1102        enumerations.sort(cmp)
1103        return enumerations
1104
1105    def __get_admin_context_vars(self):
1106        return {"enumerations": self.__get_enumerations_list()}
1107
1108    def __get_enumeration(self, id):
1109        from django.db.models import get_models
1110        models = get_models(telemeta.models)
1111        for model in models:
1112            if model._meta.module_name == id:
1113                break
1114
1115        if model._meta.module_name != id:
1116            return None
1117
1118        return model
1119
1120    @method_decorator(permission_required('telemeta.change_keyword'))
1121    def edit_enumeration(self, request, enumeration_id):
1122
1123        enumeration  = self.__get_enumeration(enumeration_id)
1124        if enumeration == None:
1125            raise Http404
1126
1127        vars = self.__get_admin_context_vars()
1128        vars["enumeration_id"] = enumeration._meta.module_name
1129        vars["enumeration_name"] = enumeration._meta.verbose_name
1130        vars["enumeration_values"] = enumeration.objects.all()
1131        return render(request, 'telemeta/enumeration_edit.html', vars)
1132
1133    @method_decorator(permission_required('telemeta.add_keyword'))
1134    def add_to_enumeration(self, request, enumeration_id):
1135
1136        enumeration  = self.__get_enumeration(enumeration_id)
1137        if enumeration == None:
1138            raise Http404
1139
1140        enumeration_value = enumeration(value=request.POST['value'])
1141        enumeration_value.save()
1142
1143        return self.edit_enumeration(request, enumeration_id)
1144
1145    @method_decorator(permission_required('telemeta.change_keyword'))
1146    def update_enumeration(self, request, enumeration_id):
1147
1148        enumeration  = self.__get_enumeration(enumeration_id)
1149        if enumeration == None:
1150            raise Http404
1151
1152        if request.method == 'POST':
1153            enumeration.objects.filter(id__in=request.POST.getlist('sel')).delete()
1154
1155        return self.edit_enumeration(request, enumeration_id)
1156
1157    @method_decorator(permission_required('telemeta.change_keyword'))
1158    def edit_enumeration_value(self, request, enumeration_id, value_id):
1159
1160        enumeration  = self.__get_enumeration(enumeration_id)
1161        if enumeration == None:
1162            raise Http404
1163
1164        vars = self.__get_admin_context_vars()
1165        vars["enumeration_id"] = enumeration._meta.module_name
1166        vars["enumeration_name"] = enumeration._meta.verbose_name
1167        vars["enumeration_record"] = enumeration.objects.get(id__exact=value_id)
1168        return render(request, 'telemeta/enumeration_edit_value.html', vars)
1169
1170    @method_decorator(permission_required('telemeta.change_keyword'))
1171    def update_enumeration_value(self, request, enumeration_id, value_id):
1172
1173        if request.method == 'POST':
1174            enumeration  = self.__get_enumeration(enumeration_id)
1175            if enumeration == None:
1176                raise Http404
1177
1178            record = enumeration.objects.get(id__exact=value_id)
1179            record.value = request.POST["value"]
1180            record.save()
1181
1182        return self.edit_enumeration(request, enumeration_id)
1183
1184
1185class InstrumentView(object):
1186    """Provide Instrument web UI methods"""
1187
1188    @method_decorator(permission_required('telemeta.change_instrument'))
1189    def edit_instrument(self, request):
1190
1191        instruments = Instrument.objects.all().order_by('name')
1192        if instruments == None:
1193            raise Http404
1194        return render(request, 'telemeta/instrument_edit.html', {'instruments': instruments})
1195
1196    @method_decorator(permission_required('telemeta.add_instrument'))
1197    def add_to_instrument(self, request):
1198
1199        if request.method == 'POST':
1200            instrument = Instrument(name=request.POST['value'])
1201            instrument.save()
1202
1203        return self.edit_instrument(request)
1204
1205    @method_decorator(permission_required('telemeta.change_instrument'))
1206    def update_instrument(self, request):
1207
1208        if request.method == 'POST':
1209            Instrument.objects.filter(id__in=request.POST.getlist('sel')).delete()
1210
1211        return self.edit_instrument(request)
1212
1213    @method_decorator(permission_required('telemeta.change_instrument'))
1214    def edit_instrument_value(self, request, value_id):
1215        instrument = Instrument.objects.get(id__exact=value_id)
1216
1217        return render(request, 'telemeta/instrument_edit_value.html', {'instrument': instrument})
1218
1219    @method_decorator(permission_required('telemeta.change_instrument'))
1220    def update_instrument_value(self, request, value_id):
1221
1222        if request.method == 'POST':
1223            instrument = Instrument.objects.get(id__exact=value_id)
1224            instrument.name = request.POST["value"]
1225            instrument.save()
1226
1227        return self.edit_instrument(request)
1228
1229
1230class GeoView(object):
1231    """Provide Geo web UI methods"""
1232
1233    def list_continents(self, request):
1234        continents = MediaItem.objects.all().countries(group_by_continent=True)
1235        return render(request, 'telemeta/geo_continents.html',
1236                    {'continents': continents, 'gmap_key': settings.TELEMETA_GMAP_KEY })
1237
1238    def country_info(self, request, id):
1239        country = Location.objects.get(pk=id)
1240        return render(request, 'telemeta/country_info.html', {
1241            'country': country, 'continent': country.continents()[0]})
1242
1243    def list_countries(self, request, continent):
1244        continent = Location.objects.by_flatname(continent)[0]
1245        countries = MediaItem.objects.by_location(continent).countries()
1246
1247        return render(request, 'telemeta/geo_countries.html', {
1248            'continent': continent,
1249            'countries': countries
1250        })
1251
1252    def list_country_collections(self, request, continent, country):
1253        continent = Location.objects.by_flatname(continent)[0]
1254        country = Location.objects.by_flatname(country)[0]
1255        objects = MediaCollection.objects.enriched().by_location(country)
1256        return list_detail.object_list(request, objects,
1257            template_name='telemeta/geo_country_collections.html', paginate_by=20,
1258            extra_context={'country': country, 'continent': continent})
1259
1260    def list_country_items(self, request, continent, country):
1261        continent = Location.objects.by_flatname(continent)[0]
1262        country = Location.objects.by_flatname(country)[0]
1263        objects = MediaItem.objects.enriched().by_location(country)
1264        return list_detail.object_list(request, objects,
1265            template_name='telemeta/geo_country_items.html', paginate_by=20,
1266            extra_context={'country': country, 'continent': continent})
1267
1268class MarkerView(object):
1269    """Provide Collections web UI methods"""
1270
1271    @jsonrpc_method('telemeta.add_marker')
1272    def add_marker(request, marker):
1273        # marker must be a dict
1274        if isinstance(marker, dict):
1275            item_id = marker['item_id']
1276            item = MediaItem.objects.get(id=item_id)
1277            m = MediaItemMarker(item=item)
1278            m.public_id = marker['public_id']
1279            m.time = float(marker['time'])
1280            m.title = marker['title']
1281            m.description = marker['description']
1282            m.author = User.objects.get(username=marker['author'])
1283            m.save()
1284            m.set_revision(request.user)
1285        else:
1286            raise 'Error : Bad marker dictionnary'
1287
1288    @jsonrpc_method('telemeta.del_marker')
1289    def del_marker(request, public_id):
1290        m = MediaItemMarker.objects.get(public_id=public_id)
1291        m.delete()
1292
1293    @jsonrpc_method('telemeta.get_markers')
1294    def get_markers(request, item_id):
1295        item = MediaItem.objects.get(id=item_id)
1296        markers = MediaItemMarker.objects.filter(item=item)
1297        list = []
1298        for marker in markers:
1299            dict = {}
1300            dict['public_id'] = marker.public_id
1301            dict['time'] = str(marker.time)
1302            dict['title'] = marker.title
1303            dict['description'] = marker.description
1304            dict['author'] = marker.author.username
1305            list.append(dict)
1306        return list
1307
1308    @jsonrpc_method('telemeta.update_marker')
1309    def update_marker(request, marker):
1310        if isinstance(marker, dict):
1311            m = MediaItemMarker.objects.get(public_id=marker['public_id'])
1312            m.time = float(marker['time'])
1313            m.title = marker['title']
1314            m.description = marker['description']
1315            m.save()
1316            m.set_revision(request.user)
1317        else:
1318            raise 'Error : Bad marker dictionnary'
1319
1320    @jsonrpc_method('telemeta.get_marker_id')
1321    def get_marker_id(request, public_id):
1322        marker = MediaItemMarker.objects.get(public_id=public_id)
1323        return marker.id
1324
1325class PlaylistView(object):
1326    """Provide Playlist web UI methods"""
1327
1328    @jsonrpc_method('telemeta.add_playlist')
1329    def add_playlist(request, playlist):
1330        # playlist must be a dict
1331        if isinstance(playlist, dict):
1332            m = Playlist()
1333            m.public_id = playlist['public_id']
1334            m.title = playlist['title']
1335            m.description = playlist['description']
1336            m.author = request.user
1337            m.save()
1338        else:
1339            raise 'Error : Bad playlist dictionnary'
1340
1341    @jsonrpc_method('telemeta.del_playlist')
1342    def del_playlist(request, public_id):
1343        m = Playlist.objects.get(public_id=public_id)
1344        m.delete()
1345
1346    @jsonrpc_method('telemeta.update_playlist')
1347    def update_playlist(request, playlist):
1348        if isinstance(playlist, dict):
1349            m = Playlist.objects.get(public_id=playlist['public_id'])
1350            m.title = playlist['title']
1351            m.description = playlist['description']
1352            m.save()
1353        else:
1354            raise 'Error : Bad playlist dictionnary'
1355
1356    @jsonrpc_method('telemeta.add_playlist_resource')
1357    def add_playlist_resource(request, playlist_id, playlist_resource):
1358        # playlist_resource must be a dict
1359        if isinstance(playlist_resource, dict):
1360            m = PlaylistResource()
1361            m.public_id = playlist_resource['public_id']
1362            m.playlist = Playlist.objects.get(public_id=playlist_id, author=request.user)
1363            m.resource_type = playlist_resource['resource_type']
1364            m.resource_id = playlist_resource['resource_id']
1365            m.save()
1366        else:
1367            raise 'Error : Bad playlist_resource dictionnary'
1368
1369    @jsonrpc_method('telemeta.del_playlist_resource')
1370    def del_playlist_resource(request, public_id):
1371        m = PlaylistResource.objects.get(public_id=public_id)
1372        m.delete()
1373
1374
1375    def playlist_csv_export(self, request, public_id, resource_type):
1376        playlist = Playlist.objects.get(public_id=public_id, author=request.user)
1377        resources = PlaylistResource.objects.filter(playlist=playlist)
1378        response = HttpResponse(mimetype='text/csv')
1379        response['Content-Disposition'] = 'attachment; filename='+playlist.title+'_'+resource_type+'.csv'
1380        writer = UnicodeWriter(response)
1381
1382        elements = []
1383        for resource in resources:
1384            if resource_type == 'items':
1385                if resource.resource_type == 'collection':
1386                    collection = MediaCollection.objects.get(id=resource.resource_id)
1387                    collection_items = MediaItem.objects.filter(collection=collection)
1388                    for item in collection_items:
1389                        elements.append(item)
1390                elif resource.resource_type == 'item':
1391                    item = MediaItem.objects.get(id=resource.resource_id)
1392                    elements.append(item)
1393
1394            elif resource_type == 'collections':
1395                if resource.resource_type == 'collection':
1396                    collection = MediaCollection.objects.get(id=resource.resource_id)
1397                    elements.append(collection)
1398
1399        if elements:
1400            element = elements[0].to_dict()
1401            tags = element.keys()
1402            # code and title on the two first column
1403            tags.remove('code')
1404            tags.remove('title')
1405            tags.sort()
1406            tags.insert(0, 'title')
1407            tags.insert(0, 'code')
1408            writer.writerow(tags)
1409
1410            for element in elements:
1411                data = []
1412                element = element.to_dict()
1413                for tag in tags:
1414                    data.append(element[tag])
1415                writer.writerow(data)
1416        return response
1417
1418
1419class ProfileView(object):
1420    """Provide Collections web UI methods"""
1421
1422    @method_decorator(login_required)
1423    def profile_detail(self, request, username, template='telemeta/profile_detail.html'):
1424        user = User.objects.get(username=username)
1425        try:
1426            profile = user.get_profile()
1427        except:
1428            profile = None
1429        playlists = get_playlists(request, user)
1430        user_revisions = get_revisions(25, user)
1431
1432        return render(request, template, {'profile' : profile, 'usr': user, 'playlists': playlists,
1433                                          'user_revisions': user_revisions})
1434
1435    @method_decorator(login_required)
1436    def profile_edit(self, request, username, template='telemeta/profile_edit.html'):
1437        if request.user.is_superuser:
1438            user_hidden_fields = ['profile-user', 'user-password', 'user-last_login', 'user-date_joined']
1439        else:
1440            user_hidden_fields = ['user-username', 'user-is_staff', 'profile-user', 'user-is_active',
1441                         'user-password', 'user-last_login', 'user-date_joined', 'user-groups',
1442                         'user-user_permissions', 'user-is_superuser', 'profile-expiration_date']
1443
1444        user = User.objects.get(username=username)
1445        if user != request.user and not request.user.is_staff:
1446            mess = ugettext('Access not allowed')
1447            title = ugettext('User profile') + ' : ' + username + ' : ' + mess
1448            description = ugettext('Please login or contact the website administator to get a private access.')
1449            messages.error(request, title)
1450            return render(request, 'telemeta/messages.html', {'description' : description})
1451
1452        try:
1453            profile = user.get_profile()
1454        except:
1455            profile = UserProfile(user=user)
1456
1457        if request.method == 'POST':
1458            user_form = UserChangeForm(request.POST, instance=user, prefix='user')
1459            profile_form = UserProfileForm(request.POST, instance=profile, prefix='profile')
1460            if user_form.is_valid() and profile_form.is_valid():
1461                user_form.save()
1462                profile_form.save()
1463                return HttpResponseRedirect('/accounts/'+username+'/profile/')
1464        else:
1465            user_form = UserChangeForm(instance=user, prefix='user')
1466            profile_form = UserProfileForm(instance=profile, prefix='profile')
1467            forms = [user_form, profile_form]
1468        return render(request, template, {'forms': forms, 'usr': user,
1469                                'user_hidden_fields': user_hidden_fields})
1470
1471
1472class LastestRevisionsFeed(Feed):
1473    "the RSS feed of the lastest revisions"
1474
1475    organization = settings.TELEMETA_ORGANIZATION
1476    subjects = settings.TELEMETA_SUBJECTS
1477    tags = ['title', 'description', 'comment']
1478    title = organization + ' - Telemeta - ' + ugettext('Last changes')
1479    link = ""
1480    description = ' '.join([subject.decode('utf-8') for subject in subjects])
1481    n_items = 100
1482
1483    def items(self):
1484        return get_revisions(self.n_items)
1485
1486    def item_title(self, r):
1487        element = r['element']
1488        if element.title == '':
1489            title = str(element.public_id)
1490        else:
1491            title = element.title
1492        return element.element_type + ' : ' + title
1493
1494    def item_description(self, r):
1495        revision = r['revision']
1496        element = r['element']
1497        description = '<b>modified by ' + revision.user.username + ' on ' + unicode(revision.time) + '</b><br /><br />'
1498        dict = element.to_dict()
1499        for tag in dict.keys():
1500            try:
1501                value = dict[tag]
1502                if value != '':
1503                    description += tag + ' : ' + value + '<br />'
1504            except:
1505                continue
1506        return description.encode('utf-8')
1507
1508    def item_link(self, r):
1509        revision = r['revision']
1510        element = r['element']
1511        if revision.element_type[-1] == 's':
1512            dir = revision.element_type
1513        else:
1514            dir = revision.element_type + 's'
1515        link = '/archives/' + dir + '/' + str(element.public_id)
1516        return link
1517
1518
1519class UserRevisionsFeed(LastestRevisionsFeed):
1520
1521    def get_object(self, request, username):
1522        return get_object_or_404(User, username=username)
1523
1524    def items(self, obj):
1525        return get_revisions(self.n_items, obj)
1526
1527
1528class ResourceView(object):
1529    """Provide Resource web UI methods"""
1530
1531    types = {'corpus':
1532                {'model': MediaCorpus,
1533                'form' : MediaCorpusForm,
1534                'related': MediaCorpusRelated,
1535                'related_form': MediaCorpusRelatedForm,
1536                'parent': MediaFonds,
1537                },
1538            'fonds':
1539                {'model': MediaFonds,
1540                'form' : MediaFondsForm,
1541                'related': MediaFondsRelated,
1542                'related_form': MediaFondsRelatedForm,
1543                'parent': None,
1544                }
1545            }
1546
1547    def setup(self, type):
1548        self.model = self.types[type]['model']
1549        self.form = self.types[type]['form']
1550        self.related = self.types[type]['related']
1551        self.related_form = self.types[type]['related_form']
1552        self.parent = self.types[type]['parent']
1553        self.type = type
1554
1555    def detail(self, request, type, public_id, template='telemeta/resource_detail.html'):
1556        self.setup(type)
1557        resource = self.model.objects.get(code=public_id)
1558        children = resource.children.all()
1559        children = children.order_by('code')
1560        related_media = self.related.objects.filter(resource=resource)
1561        check_related_media(related_media)
1562        playlists = get_playlists(request)
1563        revisions = Revision.objects.filter(element_type=type, element_id=resource.id).order_by('-time')
1564        if revisions:
1565            last_revision = revisions[0]
1566        else:
1567            last_revision = None
1568        if self.parent:
1569            parents = self.parent.objects.filter(children=resource)
1570        else:
1571            parents = []
1572
1573        return render(request, template, {'resource': resource, 'type': type, 'children': children,
1574                        'related_media': related_media, 'parents': parents, 'playlists': playlists,
1575                        'last_revision': last_revision })
1576
1577    @jsonrpc_method('telemeta.change_fonds')
1578    @jsonrpc_method('telemeta.change_corpus')
1579    def edit(self, request, type, public_id, template='telemeta/resource_edit.html'):
1580        self.setup(type)
1581        resource = self.model.objects.get(code=public_id)
1582        if request.method == 'POST':
1583            form = self.form(data=request.POST, files=request.FILES, instance=resource)
1584            if form.is_valid():
1585                code = form.cleaned_data['code']
1586                if not code:
1587                    code = public_id
1588                form.save()
1589                resource.set_revision(request.user)
1590                return HttpResponseRedirect('/archives/'+self.type+'/'+code)
1591        else:
1592            form = self.form(instance=resource)
1593        return render(request, template, {'resource': resource, 'type': type, 'form': form,})
1594
1595    @jsonrpc_method('telemeta.add_fonds')
1596    @jsonrpc_method('telemeta.add_corpus')
1597    def add(self, request, type, template='telemeta/resource_add.html'):
1598        self.setup(type)
1599        resource = self.model()
1600        if request.method == 'POST':
1601            form = self.form(data=request.POST, files=request.FILES, instance=resource)
1602            if form.is_valid():
1603                code = form.cleaned_data['code']
1604                if not code:
1605                    code = public_id
1606                form.save()
1607                resource.set_revision(request.user)
1608                return HttpResponseRedirect('/archives/'+self.type +'/'+code)
1609        else:
1610            form = self.form(instance=resource)
1611        return render(request, template, {'resource': resource, 'type': type, 'form': form,})
1612
1613    @jsonrpc_method('telemeta.add_fonds')
1614    @jsonrpc_method('telemeta.add_corpus')
1615    def copy(self, request, type, public_id, template='telemeta/resource_edit.html'):
1616        self.setup(type)
1617        if request.method == 'POST':
1618            resource = self.model()
1619            form = self.form(data=request.POST, files=request.FILES, instance=resource)
1620            if form.is_valid():
1621                code = form.cleaned_data['code']
1622                if not code:
1623                    code = public_id
1624                resource.save()
1625                resource.set_revision(request.user)
1626                return HttpResponseRedirect('/archives/'+self.type +'/'+code)
1627        else:
1628            resource = self.model.objects.get(code=public_id)
1629            form = self.form(instance=resource)
1630        return render(request, template, {'resource': resource, 'type': type, "form": form,})
1631
1632    def playlist(self, request, type, public_id, template, mimetype):
1633        self.setup(type)
1634        try:
1635            resource = self.model.objects.get(code=public_id)
1636        except ObjectDoesNotExist:
1637            raise Http404
1638
1639        template = loader.get_template(template)
1640        context = RequestContext(request, {'resource': resource, 'host': request.META['HTTP_HOST']})
1641        return HttpResponse(template.render(context), mimetype=mimetype)
1642
1643    @jsonrpc_method('telemeta.del_fonds')
1644    @jsonrpc_method('telemeta.del_corpus')
1645    def delete(self, request, type, public_id):
1646        self.setup(type)
1647        resource = self.model.objects.get(code=public_id)
1648        resource.delete()
1649        return HttpResponseRedirect('/archives/'+self.type+'/')
1650
1651    def related_stream(self, request, type, public_id, media_id):
1652        self.setup(type)
1653        resource = self.model.objects.get(code=public_id)
1654        media = self.related.objects.get(resource=resource, id=media_id)
1655        response = HttpResponse(stream_from_file(media.file.path), mimetype=media.mime_type)
1656        return response
1657
1658    @jsonrpc_method('telemeta.add_fonds_related_media')
1659    @jsonrpc_method('telemeta.add_corpus_related_media')
1660    def related_edit(self, request, type, public_id, template):
1661        self.setup(type)
1662        resource = self.model.objects.get(code=public_id)
1663        ResourceRelatedFormSet = inlineformset_factory(self.model, self.related, form=self.related_form)
1664        if request.method == 'POST':
1665            formset = ResourceRelatedFormSet(data=request.POST, files=request.FILES, instance=resource)
1666            if formset.is_valid():
1667                formset.save()
1668                resource.set_revision(request.user)
1669                return HttpResponseRedirect('/archives/'+self.type+'/'+public_id)
1670        else:
1671            formset = ResourceRelatedFormSet(instance=resource)
1672        return render(request, template, {'resource': resource, 'type': type, 'formset': formset,})
1673
1674
Note: See TracBrowser for help on using the repository browser.