]> git.parisson.com Git - telemeta.git/commitdiff
split web.base into views.*
authoryomguy <yomguy@parisson.com>
Mon, 17 Sep 2012 12:43:02 +0000 (14:43 +0200)
committeryomguy <yomguy@parisson.com>
Mon, 17 Sep 2012 12:43:02 +0000 (14:43 +0200)
15 files changed:
telemeta/urls.py
telemeta/views/__init__.py
telemeta/views/admin.py [new file with mode: 0644]
telemeta/views/base.py [deleted file]
telemeta/views/collection.py [new file with mode: 0644]
telemeta/views/core.py [new file with mode: 0644]
telemeta/views/feed.py [new file with mode: 0644]
telemeta/views/geo.py [new file with mode: 0644]
telemeta/views/home.py [new file with mode: 0644]
telemeta/views/instrument.py [new file with mode: 0644]
telemeta/views/item.py [new file with mode: 0644]
telemeta/views/marker.py [new file with mode: 0644]
telemeta/views/playlist.py [new file with mode: 0644]
telemeta/views/profile.py [new file with mode: 0644]
telemeta/views/ressource.py [new file with mode: 0644]

index e9c1a74c738f74191cf5edbfb0cb551d19ea90db..8b762f8f92d4a03d08636157fb2cfb9c74d69aa0 100644 (file)
@@ -38,7 +38,7 @@ from django.conf.urls.defaults import *
 from django.conf import settings
 from django.views.generic.simple import redirect_to
 from telemeta.models import MediaItem, MediaCollection, MediaItemMarker, MediaCorpus, MediaFonds
-from telemeta.views.base import GeneralView, AdminView, CollectionView, ItemView, \
+from telemeta.views import HomeView, AdminView, CollectionView, ItemView, \
                                 InstrumentView, PlaylistView, ProfileView, GeoView, \
                                 LastestRevisionsFeed, ResourceView, UserRevisionsFeed
 from jsonrpc import jsonrpc_site
@@ -48,7 +48,7 @@ import telemeta.config
 telemeta.config.check()
 
 # initialization
-general_view = GeneralView()
+general_view = HomeView()
 admin_view = AdminView()
 collection_view = CollectionView()
 item_view = ItemView()
index 40a96afc6ff09d58a702b76e3f7dd412fe975e26..e4b9ff2b9ce9969324c87fc1de067730736e0955 100644 (file)
@@ -1 +1,47 @@
 # -*- coding: utf-8 -*-
+# Copyright (C) 2010-2012 Parisson SARL
+
+# This software is a computer program whose purpose is to backup, analyse,
+# transcode and stream any audio content with its metadata over a views frontend.
+
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+
+# Authors: Guillaume Pellerin <yomguy@parisson.com>
+
+from telemeta.views.core import *
+from telemeta.views.home import *
+from telemeta.views.item import *
+from telemeta.views.collection import *
+from telemeta.views.admin import *
+from telemeta.views.instrument import *
+from telemeta.views.geo import *
+from telemeta.views.marker import *
+from telemeta.views.playlist import *
+from telemeta.views.profile import *
+from telemeta.views.feed import *
+from telemeta.views.ressource import *
+
diff --git a/telemeta/views/admin.py b/telemeta/views/admin.py
new file mode 100644 (file)
index 0000000..d483c89
--- /dev/null
@@ -0,0 +1,152 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2007-2010 Samalyse SARL
+# Copyright (C) 2010-2012 Parisson SARL
+
+# This software is a computer program whose purpose is to backup, analyse,
+# transcode and stream any audio content with its metadata over a web frontend.
+
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+
+# Authors: Olivier Guilyardi <olivier@samalyse.com>
+#          Guillaume Pellerin <yomguy@parisson.com>
+
+
+from telemeta.views.core import *
+
+class AdminView(object):
+    """Provide Admin web UI methods"""
+
+    @method_decorator(permission_required('is_superuser'))
+    def admin_index(self, request):
+        return render(request, 'telemeta/admin.html', self.__get_admin_context_vars())
+
+    @method_decorator(permission_required('is_superuser'))
+    def admin_general(self, request):
+        return render(request, 'telemeta/admin_general.html', self.__get_admin_context_vars())
+
+    @method_decorator(permission_required('is_superuser'))
+    def admin_enumerations(self, request):
+        return render(request, 'telemeta/admin_enumerations.html', self.__get_admin_context_vars())
+
+    @method_decorator(permission_required('is_superuser'))
+    def admin_users(self, request):
+        users = User.objects.all()
+        return render(request, 'telemeta/admin_users.html', {'users': users})
+
+    def __get_enumerations_list(self):
+        from django.db.models import get_models
+        models = get_models(telemeta.models)
+
+        enumerations = []
+        for model in models:
+            if issubclass(model, Enumeration):
+                enumerations.append({"name": model._meta.verbose_name,
+                    "id": model._meta.module_name})
+
+        cmp = lambda obj1, obj2: unaccent_icmp(obj1['name'], obj2['name'])
+        enumerations.sort(cmp)
+        return enumerations
+
+    def __get_admin_context_vars(self):
+        return {"enumerations": self.__get_enumerations_list()}
+
+    def __get_enumeration(self, id):
+        from django.db.models import get_models
+        models = get_models(telemeta.models)
+        for model in models:
+            if model._meta.module_name == id:
+                break
+
+        if model._meta.module_name != id:
+            return None
+
+        return model
+
+    @method_decorator(permission_required('telemeta.change_keyword'))
+    def edit_enumeration(self, request, enumeration_id):
+
+        enumeration  = self.__get_enumeration(enumeration_id)
+        if enumeration == None:
+            raise Http404
+
+        vars = self.__get_admin_context_vars()
+        vars["enumeration_id"] = enumeration._meta.module_name
+        vars["enumeration_name"] = enumeration._meta.verbose_name
+        vars["enumeration_values"] = enumeration.objects.all()
+        return render(request, 'telemeta/enumeration_edit.html', vars)
+
+    @method_decorator(permission_required('telemeta.add_keyword'))
+    def add_to_enumeration(self, request, enumeration_id):
+
+        enumeration  = self.__get_enumeration(enumeration_id)
+        if enumeration == None:
+            raise Http404
+
+        enumeration_value = enumeration(value=request.POST['value'])
+        enumeration_value.save()
+
+        return self.edit_enumeration(request, enumeration_id)
+
+    @method_decorator(permission_required('telemeta.change_keyword'))
+    def update_enumeration(self, request, enumeration_id):
+
+        enumeration  = self.__get_enumeration(enumeration_id)
+        if enumeration == None:
+            raise Http404
+
+        if request.method == 'POST':
+            enumeration.objects.filter(id__in=request.POST.getlist('sel')).delete()
+
+        return self.edit_enumeration(request, enumeration_id)
+
+    @method_decorator(permission_required('telemeta.change_keyword'))
+    def edit_enumeration_value(self, request, enumeration_id, value_id):
+
+        enumeration  = self.__get_enumeration(enumeration_id)
+        if enumeration == None:
+            raise Http404
+
+        vars = self.__get_admin_context_vars()
+        vars["enumeration_id"] = enumeration._meta.module_name
+        vars["enumeration_name"] = enumeration._meta.verbose_name
+        vars["enumeration_record"] = enumeration.objects.get(id__exact=value_id)
+        return render(request, 'telemeta/enumeration_edit_value.html', vars)
+
+    @method_decorator(permission_required('telemeta.change_keyword'))
+    def update_enumeration_value(self, request, enumeration_id, value_id):
+
+        if request.method == 'POST':
+            enumeration  = self.__get_enumeration(enumeration_id)
+            if enumeration == None:
+                raise Http404
+
+            record = enumeration.objects.get(id__exact=value_id)
+            record.value = request.POST["value"]
+            record.save()
+
+        return self.edit_enumeration(request, enumeration_id)
+
diff --git a/telemeta/views/base.py b/telemeta/views/base.py
deleted file mode 100644 (file)
index 30ad004..0000000
+++ /dev/null
@@ -1,1733 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (C) 2007-2010 Samalyse SARL
-# Copyright (C) 2010-2012 Parisson SARL
-
-# This software is a computer program whose purpose is to backup, analyse,
-# transcode and stream any audio content with its metadata over a web frontend.
-
-# This software is governed by the CeCILL  license under French law and
-# abiding by the rules of distribution of free software.  You can  use,
-# modify and/ or redistribute the software under the terms of the CeCILL
-# license as circulated by CEA, CNRS and INRIA at the following URL
-# "http://www.cecill.info".
-
-# As a counterpart to the access to the source code and  rights to copy,
-# modify and redistribute granted by the license, users are provided only
-# with a limited warranty  and the software's author,  the holder of the
-# economic rights,  and the successive licensors  have only  limited
-# liability.
-
-# In this respect, the user's attention is drawn to the risks associated
-# with loading,  using,  modifying and/or developing or reproducing the
-# software by the user in light of its specific status of free software,
-# that may mean  that it is complicated to manipulate,  and  that  also
-# therefore means  that it is reserved for developers  and  experienced
-# professionals having in-depth computer knowledge. Users are therefore
-# encouraged to load and test the software's suitability as regards their
-# requirements in conditions enabling the security of their systems and/or
-# data to be ensured and,  more generally, to use and operate it in the
-# same conditions as regards security.
-
-# The fact that you are presently reading this means that you have had
-# knowledge of the CeCILL license and that you accept its terms.
-
-# Authors: Olivier Guilyardi <olivier@samalyse.com>
-#          Guillaume Pellerin <yomguy@parisson.com>
-
-import re
-import os
-import sys
-import csv
-import time
-import random
-import datetime
-import timeside
-
-from jsonrpc import jsonrpc_method
-
-from django.utils.decorators import method_decorator
-from django.contrib.auth import authenticate, login
-from django.template import RequestContext, loader
-from django import template
-from django.http import HttpResponse, HttpResponseRedirect
-from django.http import Http404
-from django.shortcuts import render_to_response, redirect, get_object_or_404
-from django.views.generic import list_detail
-from django.views.generic import DetailView
-from django.conf import settings
-from django.contrib import auth
-from django.contrib import messages
-from django.contrib.auth.decorators import login_required, permission_required
-from django.core.context_processors import csrf
-from django.forms.models import modelformset_factory, inlineformset_factory
-from django.contrib.auth.models import User
-from django.utils.translation import ugettext
-from django.contrib.auth.forms import UserChangeForm
-from django.core.exceptions import ObjectDoesNotExist
-from django.contrib.syndication.views import Feed
-from django.core.servers.basehttp import FileWrapper
-
-from telemeta.models import *
-import telemeta.models
-import telemeta.interop.oai as oai
-from telemeta.interop.oaidatasource import TelemetaOAIDataSource
-from telemeta.util.unaccent import unaccent
-from telemeta.util.unaccent import unaccent_icmp
-from telemeta.util.logger import Logger
-from telemeta.util.unicode import UnicodeWriter
-from telemeta.cache import TelemetaCache
-import telemeta.views.pages as pages
-from telemeta.forms import *
-
-# Model type definition
-mods = {'item': MediaItem, 'collection': MediaCollection,
-        'corpus': MediaCorpus, 'fonds': MediaFonds, 'marker': MediaItemMarker, }
-
-# TOOLS
-
-class FixedFileWrapper(FileWrapper):
-    def __iter__(self):
-        self.filelike.seek(0)
-        return self
-
-def send_file(request, filename, content_type='image/jpeg'):
-    """
-    Send a file through Django without loading the whole file into
-    memory at once. The FileWrapper will turn the file object into an
-    iterator for chunks of 8KB.
-    """
-    wrapper = FixedFileWrapper(file(filename, 'rb'))
-    response = HttpResponse(wrapper, content_type=content_type)
-    response['Content-Length'] = os.path.getsize(filename)
-    return response
-
-def render(request, template, data = None, mimetype = None):
-    return render_to_response(template, data, context_instance=RequestContext(request),
-                              mimetype=mimetype)
-
-def stream_from_processor(__decoder, __processor, __flag, metadata=None):
-    while True:
-        __frames, __eodproc = __processor.process(*__decoder.process())
-        if __eodproc or not len(__frames):
-            if metadata:
-                __processor.set_metadata(metadata)
-                __processor.write_metadata()
-            __flag.value = True
-            __flag.save()
-            break
-        yield __processor.chunk
-
-def stream_from_file(__file):
-    chunk_size = 0x100000
-    f = open(__file, 'r')
-    while True:
-        __chunk = f.read(chunk_size)
-        if not len(__chunk):
-            f.close()
-            break
-        yield __chunk
-
-def get_public_access(access, year_from=None, year_to=None):
-    # Rolling publishing date : public access is given when time between recorded year
-    # and current year is over the settings value PUBLIC_ACCESS_PERIOD
-    if year_from and not year_from == 0:
-        year = year_from
-    elif year_to and not year_to == 0:
-        year = year_to
-    else:
-        year = 0
-    if access == 'full':
-        public_access = True
-    else:
-        public_access = False
-        if year and not year == 'None':
-            year_now = datetime.datetime.now().strftime("%Y")
-            if int(year_now) - int(year) >= settings.TELEMETA_PUBLIC_ACCESS_PERIOD:
-                public_access = True
-        else:
-            public_access = False
-    return public_access
-
-def get_revisions(nb, user=None):
-    last_revisions = Revision.objects.order_by('-time')
-    if user:
-        last_revisions = last_revisions.filter(user=user)
-    last_revisions = last_revisions[0:nb]
-    revisions = []
-
-    for revision in last_revisions:
-        for type in mods.keys():
-            if revision.element_type == type:
-                try:
-                    element = mods[type].objects.get(pk=revision.element_id)
-                except:
-                    element = None
-        if not element == None:
-            revisions.append({'revision': revision, 'element': element})
-    return revisions
-
-def get_playlists(request, user=None):
-    if not user:
-        user = request.user
-    playlists = []
-    if user.is_authenticated():
-        user_playlists = Playlist.objects.filter(author=user)
-        for playlist in user_playlists:
-            playlist_resources = PlaylistResource.objects.filter(playlist=playlist)
-            resources = []
-            for resource in playlist_resources:
-                try:
-                    for type in mods.keys():
-                        if resource.resource_type == type:
-                            element = mods[type].objects.get(id=resource.resource_id)
-                except:
-                    element = None
-                resources.append({'element': element, 'type': resource.resource_type, 'public_id': resource.public_id })
-            playlists.append({'playlist': playlist, 'resources': resources})
-    return playlists
-
-def check_related_media(medias):
-    for media in medias:
-        if not media.mime_type:
-            media.set_mime_type()
-            media.save()
-        if not media.title and media.url:
-            if 'https' in media.url:
-                media.url = media.url.replace('https', 'http')
-            import lxml.etree
-            parser = lxml.etree.HTMLParser()
-            tree = lxml.etree.parse(media.url, parser)
-            try:
-                title = tree.find(".//title").text
-            except:
-                title = media.url
-            media.title = title.replace('\n', '').strip()
-            media.save()
-
-def auto_code(resources, base_code):
-    index = 1
-    while True:
-        code = base_code + '_' + str(index)
-        r = resources.filter(code=code)
-        if not r:
-            break
-        index += 1
-    return code
-
-
-class GeneralView(object):
-    """Provide general web UI methods"""
-
-    def home(self, request):
-        """Render the index page"""
-
-        template = loader.get_template('telemeta/home.html')
-
-        sound_items = MediaItem.objects.sound()
-        _sound_pub_items = []
-        for item in sound_items:
-            if get_public_access(item.public_access,  str(item.recorded_from_date).split('-')[0],
-                                            str(item.recorded_to_date).split('-')[0]):
-                _sound_pub_items.append(item)
-
-        random.shuffle(_sound_pub_items)
-        if len(_sound_pub_items) != 0:
-            sound_pub_item = _sound_pub_items[0]
-        else:
-            sound_pub_item = None
-        if len(_sound_pub_items) == 2:
-            sound_pub_items = [_sound_pub_items[1]]
-        elif len(_sound_pub_items) > 2:
-            sound_pub_items = _sound_pub_items[1:3]
-        else:
-            sound_pub_items = None
-
-        revisions = get_revisions(25)
-        context = RequestContext(request, {
-                    'page_content': pages.get_page_content(request, 'home', ignore_slash_issue=True),
-                    'revisions': revisions,  'sound_pub_items': sound_pub_items,
-                    'sound_pub_item': sound_pub_item })
-        return HttpResponse(template.render(context))
-
-    def lists(self, request):
-        """Render the home page"""
-
-        if request.user.is_authenticated():
-            template='telemeta/lists.html'
-            playlists = get_playlists(request)
-            revisions = get_revisions(100)
-            searches = Search.objects.filter(username=request.user)
-            user_revisions = get_revisions(25, request.user)
-            return render(request, template, {'playlists': playlists, 'searches': searches,
-                                              'revisions': revisions, 'user_revisions': user_revisions })
-        else:
-            template = 'telemeta/messages.html'
-            mess = ugettext('Access not allowed')
-            title = ugettext('Lists') + ' : ' + mess
-            description = ugettext('Please login or contact the website administator to get a private access.')
-            messages.error(request, title)
-            return render(request, template, {'description' : description})
-
-    def edit_search(self, request, criteria=None):
-        year_min, year_max = MediaCollection.objects.all().recording_year_range()
-        rec_years = year_min and year_max and range(year_min, year_max + 1) or []
-        year_min, year_max = MediaCollection.objects.all().publishing_year_range()
-        pub_years = year_min and year_max and range(year_min, year_max + 1) or []
-        if request.user.is_authenticated():
-            searches = Search.objects.filter(username=request.user)
-        else:
-            searches = []
-        return render(request, 'telemeta/search_criteria.html', {
-            'rec_years': rec_years,
-            'pub_years': pub_years,
-            'ethnic_groups': MediaItem.objects.all().ethnic_groups(),
-            'criteria': criteria,
-            'searches': searches,
-        })
-
-    def handle_oai_request(self, request):
-        host = request.META['HTTP_HOST']
-        datasource  = TelemetaOAIDataSource()
-        repository_name = settings.TELEMETA_ORGANIZATION
-        url         = 'http://' + host + request.path
-        admin       = settings.ADMINS[0][1]
-        provider    = oai.DataProvider(datasource, repository_name, url, admin)
-        args        = request.GET.copy()
-        args.update(request.POST)
-        return HttpResponse(provider.handle(args), mimetype='text/xml')
-
-    def render_flatpage(self, request, path):
-        try:
-            content = pages.get_page_content(request, path)
-        except pages.MalformedPagePath:
-            return redirect(request.path + '/')
-
-        if isinstance(content, pages.PageAttachment):
-            return HttpResponse(content, content.mimetype())
-        else:
-            return render(request, 'telemeta/flatpage.html', {'page_content': content })
-
-    def logout(self, request):
-        auth.logout(request)
-        return redirect('telemeta-home')
-
-    def search(self, request, type = None):
-        """Perform a search through collections and items metadata"""
-        collections = MediaCollection.objects.enriched()
-        items = MediaItem.objects.enriched()
-        corpus = MediaCorpus.objects.all()
-        fonds  = MediaFonds.objects.all()
-        input = request.REQUEST
-        criteria = {}
-
-        switch = {
-            'pattern': lambda value: (
-                collections.quick_search(value),
-                items.quick_search(value),
-                corpus.quick_search(value),
-                fonds.quick_search(value),
-                ),
-            'title': lambda value: (
-                collections.word_search('title', value),
-                items.by_title(value),
-                corpus.word_search('title', value),
-                fonds.word_search('title', value)),
-            'location': lambda value: (
-                collections.by_location(Location.objects.get(name=value)),
-                items.by_location(Location.objects.get(name=value))),
-            'continent': lambda value: (
-                collections.by_continent(value),
-                items.filter(continent = value)),
-            'ethnic_group': lambda value: (
-                collections.by_ethnic_group(value),
-                items.filter(ethnic_group = value),
-                EthnicGroup.objects.get(pk=value)),
-            'creator': lambda value: (
-                collections.word_search('creator', value),
-                items.word_search('collection__creator', value)),
-            'collector': lambda value: (
-                collections.by_fuzzy_collector(value),
-                items.by_fuzzy_collector(value)),
-            'rec_year_from': lambda value: (
-                collections.by_recording_year(int(value), int(input.get('rec_year_to', value))),
-                items.by_recording_date(datetime.date(int(value), 1, 1),
-                                        datetime.date(int(input.get('rec_year_to', value)), 12, 31))),
-            'rec_year_to': lambda value: (collections, items),
-            'pub_year_from': lambda value: (
-                collections.by_publish_year(int(value), int(input.get('pub_year_to', value))),
-                items.by_publish_year(int(value), int(input.get('pub_year_to', value)))),
-            'pub_year_to': lambda value: (collections, items),
-            'sound': lambda value: (
-                collections.sound(),
-                items.sound()),
-            'instrument': lambda value: (
-                collections.by_instrument(value),
-                items.by_instrument(value)),
-        }
-
-        for key, value in input.items():
-            func = switch.get(key)
-            if func and value and value != "0":
-                try:
-                    res = func(value)
-                    if len(res)  > 4:
-                        collections, items, corpus, fonds, value = res
-                    elif len(res) == 4:
-                        collections, items, corpus, fonds = res
-                    elif len(res) == 3:
-                        collections, items, value = res
-                        corpus = corpus.none()
-                        fonds = fonds.none()
-                    else:
-                        collections, items = res
-                        corpus = corpus.none()
-                        fonds = fonds.none()
-
-                except ObjectDoesNotExist:
-                    collections = collections.none()
-                    items = items.none()
-                    corpus = corpus.none()
-                    fonds = fonds.none()
-
-                criteria[key] = value
-
-        # Save the search
-        user = request.user
-        if user:
-            if user.is_authenticated():
-                search = Search(username=user)
-                search.save()
-                if criteria:
-                    for key in criteria.keys():
-                        value = criteria[key]
-                        if key == 'ethnic_group':
-                            try:
-                                group = EthnicGroup.objects.get(value=value)
-                                value = group.id
-                            except:
-                                value = ''
-                        criter = Criteria(key=key, value=value)
-                        criter.save()
-                        search.criteria.add(criter)
-                    search.save()
-
-        if type is None:
-            if collections.count():
-                type = 'collections'
-            else:
-                type = 'items'
-
-        if type == 'items':
-            objects = items
-        elif type == 'collections':
-            objects = collections
-        elif type == 'corpus':
-            objects = corpus
-        elif type == 'fonds':
-            objects = fonds
-
-        return list_detail.object_list(request, objects,
-            template_name='telemeta/search_results.html', paginate_by=20,
-            extra_context={'criteria': criteria, 'collections_num': collections.count(),
-                'items_num': items.count(), 'corpus_num': corpus.count(), 'fonds_num': fonds.count(),
-                'type' : type,})
-
-    def complete_location(self, request, with_items=True):
-        input = request.REQUEST
-
-        token = input['q']
-        limit = int(input['limit'])
-        if with_items:
-            locations = MediaItem.objects.all().locations()
-        else:
-            locations = Location.objects.all()
-
-        locations = locations.filter(name__istartswith=token).order_by('name')[:limit]
-        data = [unicode(l) + " (%d items)" % l.items().count() for l in locations]
-
-        return HttpResponse("\n".join(data))
-
-    @method_decorator(login_required)
-    def users(self, request):
-        users = User.objects.all().order_by('last_name')
-        return render(request, 'telemeta/users.html', {'users': users})
-
-class CollectionView(object):
-    """Provide Collections web UI methods"""
-
-    def collection_detail(self, request, public_id, template='telemeta/collection_detail.html'):
-        collection = MediaCollection.objects.get(public_id=public_id)
-        items = collection.items.enriched()
-        items = items.order_by('code', 'old_code')
-
-        if collection.public_access == 'none' and not (request.user.is_staff or request.user.is_superuser):
-            mess = ugettext('Access not allowed')
-            title = ugettext('Collection') + ' : ' + public_id + ' : ' + mess
-            description = ugettext('Please login or contact the website administator to get a private access.')
-            messages.error(request, title)
-            return render(request, 'telemeta/messages.html', {'description' : description})
-
-        public_access = get_public_access(collection.public_access, collection.recorded_from_year,
-                                                collection.recorded_to_year)
-        playlists = get_playlists(request)
-
-        related_media = MediaCollectionRelated.objects.filter(collection=collection)
-        check_related_media(related_media)
-        parents = MediaCorpus.objects.filter(children=collection)
-        revisions = Revision.objects.filter(element_type='collection',
-                                            element_id=collection.id).order_by('-time')
-        if revisions:
-            last_revision = revisions[0]
-        else:
-            last_revision = None
-
-        return render(request, template, {'collection': collection, 'playlists': playlists,
-                'public_access': public_access, 'items': items, 'related_media': related_media,
-                'parents': parents, 'last_revision': last_revision })
-
-    @method_decorator(permission_required('telemeta.change_mediacollection'))
-    def collection_edit(self, request, public_id, template='telemeta/collection_edit.html'):
-        collection = MediaCollection.objects.get(public_id=public_id)
-        if request.method == 'POST':
-            form = MediaCollectionForm(data=request.POST, files=request.FILES, instance=collection)
-            if form.is_valid():
-                code = form.cleaned_data['code']
-                if not code:
-                    code = public_id
-                form.save()
-                collection.set_revision(request.user)
-                return HttpResponseRedirect('/archives/collections/'+code)
-        else:
-            form = MediaCollectionForm(instance=collection)
-
-        return render(request, template, {'collection': collection, "form": form,})
-
-    @method_decorator(permission_required('telemeta.add_mediacollection'))
-    def collection_add(self, request, template='telemeta/collection_add.html'):
-        collection = MediaCollection()
-        if request.method == 'POST':
-            form = MediaCollectionForm(data=request.POST, files=request.FILES, instance=collection)
-            if form.is_valid():
-                code = form.cleaned_data['code']
-                if not code:
-                    code = public_id
-                form.save()
-                collection.set_revision(request.user)
-                return HttpResponseRedirect('/archives/collections/'+code)
-        else:
-            form = MediaCollectionForm(instance=collection)
-
-        return render(request, template, {'collection': collection, "form": form,})
-
-    @method_decorator(permission_required('telemeta.add_mediacollection'))
-    def collection_copy(self, request, public_id, template='telemeta/collection_edit.html'):
-        if request.method == 'POST':
-            collection = MediaCollection()
-            form = MediaCollectionForm(data=request.POST, files=request.FILES, instance=collection)
-            if form.is_valid():
-                code = form.cleaned_data['code']
-                if not code:
-                    code = public_id
-                form.save()
-                collection.set_revision(request.user)
-                return HttpResponseRedirect('/archives/collections/'+code)
-        else:
-            collection = MediaCollection.objects.get(public_id=public_id)
-            form = MediaCollectionForm(instance=collection)
-
-        return render(request, template, {'collection': collection, "form": form,})
-
-    def collection_playlist(self, request, public_id, template, mimetype):
-        try:
-            collection = MediaCollection.objects.get(public_id=public_id)
-        except ObjectDoesNotExist:
-            raise Http404
-
-        template = loader.get_template(template)
-        context = RequestContext(request, {'collection': collection, 'host': request.META['HTTP_HOST']})
-        return HttpResponse(template.render(context), mimetype=mimetype)
-
-    @method_decorator(permission_required('telemeta.delete_mediacollection'))
-    def collection_delete(self, request, public_id):
-        """Delete a given collection"""
-        collection = MediaCollection.objects.get(public_id=public_id)
-        collection.delete()
-        return HttpResponseRedirect('/archives/collections/')
-
-    def related_media_collection_stream(self, request, collection_public_id, media_id):
-        collection = MediaCollection.objects.get(public_id=collection_public_id)
-        media = MediaCollectionRelated.objects.get(collection=collection, id=media_id)
-        response = HttpResponse(stream_from_file(media.file.path), mimetype=media.mime_type)
-#        response['Content-Disposition'] = 'attachment'
-        return response
-
-    @method_decorator(permission_required('telemeta.change_mediacollection'))
-    def related_media_edit(self, request, public_id, template):
-        collection = MediaCollection.objects.get(public_id=public_id)
-        MediaCollectionRelatedFormSet = inlineformset_factory(MediaCollection, MediaCollectionRelated, form=MediaCollectionRelatedForm)
-        if request.method == 'POST':
-            formset = MediaCollectionRelatedFormSet(data=request.POST, files=request.FILES, instance=collection)
-            if formset.is_valid():
-                formset.save()
-                collection.set_revision(request.user)
-                return HttpResponseRedirect('/archives/collections/'+public_id)
-        else:
-            formset = MediaCollectionRelatedFormSet(instance=collection)
-
-        return render(request, template, {'collection': collection, 'formset': formset,})
-
-class ItemView(object):
-    """Provide Collections web UI methods"""
-
-    graphers = timeside.core.processors(timeside.api.IGrapher)
-    decoders = timeside.core.processors(timeside.api.IDecoder)
-    encoders = timeside.core.processors(timeside.api.IEncoder)
-    analyzers = timeside.core.processors(timeside.api.IAnalyzer)
-    cache_data = TelemetaCache(settings.TELEMETA_DATA_CACHE_DIR)
-    cache_export = TelemetaCache(settings.TELEMETA_EXPORT_CACHE_DIR)
-
-    def item_previous_next(self, item):
-        # Get previous and next items
-        pks = []
-        items = MediaItem.objects.filter(collection=item.collection)
-        items = items.order_by('code', 'old_code')
-
-        if len(items) > 1:
-            for it in items:
-                pks.append(it.pk)
-            for pk in pks:
-                if pk == item.pk:
-                    if pk == pks[0]:
-                        previous_pk = pks[-1]
-                        next_pk = pks[1]
-                    elif pk == pks[-1]:
-                        previous_pk = pks[-2]
-                        next_pk = pks[0]
-                    else:
-                        previous_pk = pks[pks.index(pk)-1]
-                        next_pk = pks[pks.index(pk)+1]
-                    for it in items:
-                        if it.pk == previous_pk:
-                            previous = it
-                        if it.pk == next_pk:
-                            next = it
-                    previous = previous.public_id
-                    next = next.public_id
-        else:
-             previous = item.public_id
-             next = item.public_id
-
-        return previous, next
-
-    def item_detail(self, request, public_id=None, marker_id=None, width=None, height=None,
-                        template='telemeta/mediaitem_detail.html'):
-        """Show the details of a given item"""
-
-        if not public_id and marker_id:
-            marker = MediaItemMarker.objects.get(public_id=marker_id)
-            item_id = marker.item_id
-            item = MediaItem.objects.get(id=item_id)
-        else:
-            item = MediaItem.objects.get(public_id=public_id)
-
-        item_public_access = item.public_access != 'none' or item.collection.public_access != 'none'
-        if not item_public_access and not (request.user.is_staff or request.user.is_superuser):
-            mess = ugettext('Access not allowed')
-            title = ugettext('Item') + ' : ' + public_id + ' : ' + mess
-            description = ugettext('Please login or contact the website administator to get a private access.')
-            messages.error(request, title)
-            return render(request, 'telemeta/messages.html', {'description' : description})
-
-        # Get TimeSide processors
-        formats = []
-        for encoder in self.encoders:
-            if settings.TELEMETA_DOWNLOAD_FORMATS:
-                if encoder.file_extension() in settings.TELEMETA_DOWNLOAD_FORMATS:
-                    formats.append({'name': encoder.format(), 'extension': encoder.file_extension()})
-            else:
-                formats.append({'name': encoder.format(), 'extension': encoder.file_extension()})
-
-        graphers = []
-        for grapher in self.graphers:
-            graphers.append({'name':grapher.name(), 'id': grapher.id()})
-        if request.REQUEST.has_key('grapher_id'):
-            grapher_id = request.REQUEST['grapher_id']
-        else:
-            try:
-                grapher_id = settings.TELEMETA_DEFAULT_GRAPHER_ID
-            except:
-                grapher_id = 'waveform'
-
-        previous, next = self.item_previous_next(item)
-        mime_type = self.item_analyze(item)
-        #FIXME: use mimetypes.guess_type
-        if 'quicktime' in mime_type:
-            mime_type = 'video/mp4'
-
-        playlists = get_playlists(request)
-        public_access = get_public_access(item.public_access, str(item.recorded_from_date).split('-')[0],
-                                                str(item.recorded_to_date).split('-')[0])
-
-        related_media = MediaItemRelated.objects.filter(item=item)
-        check_related_media(related_media)
-        revisions = Revision.objects.filter(element_type='item', element_id=item.id).order_by('-time')
-        if revisions:
-            last_revision = revisions[0]
-        else:
-            last_revision = None
-
-        format = ''
-        if Format.objects.filter(item=item):
-            format = item.format.get()
-
-        return render(request, template,
-                    {'item': item, 'export_formats': formats,
-                    'visualizers': graphers, 'visualizer_id': grapher_id,
-                    'audio_export_enabled': getattr(settings, 'TELEMETA_DOWNLOAD_ENABLED', True),
-                    'previous' : previous, 'next' : next, 'marker': marker_id, 'playlists' : playlists,
-                    'public_access': public_access, 'width': width, 'height': height,
-                    'related_media': related_media, 'mime_type': mime_type, 'last_revision': last_revision,
-                    'format': format,
-                    })
-
-    @method_decorator(permission_required('telemeta.change_mediaitem'))
-    def item_edit(self, request, public_id, template='telemeta/mediaitem_edit.html'):
-        """Edit a given item"""
-        item = MediaItem.objects.get(public_id=public_id)
-
-        formats = []
-        for encoder in self.encoders:
-            #FIXME: timeside cannot encode to FLAC and OGG now :'(
-            if encoder.file_extension() != 'ogg' and encoder.file_extension() != 'flac':
-                formats.append({'name': encoder.format(), 'extension': encoder.file_extension()})
-
-        graphers = []
-        for grapher in self.graphers:
-            graphers.append({'name':grapher.name(), 'id': grapher.id()})
-        if request.REQUEST.has_key('grapher_id'):
-            grapher_id = request.REQUEST['grapher_id']
-        else:
-            try:
-                grapher_id = settings.TELEMETA_DEFAULT_GRAPHER_ID
-            except:
-                grapher_id = 'waveform'
-
-        previous, next = self.item_previous_next(item)
-        mime_type = self.item_analyze(item)
-        #FIXME: use mimetypes.guess_type
-        if 'quicktime' in mime_type:
-            mime_type = 'video/mp4'
-
-        format, created = Format.objects.get_or_create(item=item)
-
-        if request.method == 'POST':
-            item_form = MediaItemForm(data=request.POST, files=request.FILES, instance=item, prefix='item')
-            format_form = FormatForm(data=request.POST, instance=format, prefix='format')
-            if item_form.is_valid() and format_form.is_valid():
-                item_form.save()
-                format_form.save()
-                code = item_form.cleaned_data['code']
-                if not code:
-                    code = str(item.id)
-                if item_form.files:
-                    self.cache_data.delete_item_data(code)
-                    self.cache_export.delete_item_data(code)
-                    flags = MediaItemTranscodingFlag.objects.filter(item=item)
-                    analyses = MediaItemAnalysis.objects.filter(item=item)
-                    for flag in flags:
-                        flag.delete()
-                    for analysis in analyses:
-                        analysis.delete()
-                item.set_revision(request.user)
-                return HttpResponseRedirect('/archives/items/'+code)
-        else:
-            item_form = MediaItemForm(instance=item, prefix='item')
-            format_form = FormatForm(instance=format, prefix='format')
-
-        forms = [item_form, format_form]
-        hidden_fields = ['item-copied_from_item', 'format-item']
-
-        return render(request, template,
-                    {'item': item, 'export_formats': formats,
-                    'visualizers': graphers, 'visualizer_id': grapher_id,
-                    'audio_export_enabled': getattr(settings, 'TELEMETA_DOWNLOAD_ENABLED', True),
-                    'forms': forms, 'previous' : previous, 'next' : next, 'mime_type': mime_type,
-                    'hidden_fields': hidden_fields,
-                    })
-
-    def related_media_item_stream(self, request, item_public_id, media_id):
-        item = MediaItem.objects.get(public_id=item_public_id)
-        media = MediaItemRelated.objects.get(item=item, id=media_id)
-        response = HttpResponse(stream_from_file(media.file.path), mimetype=media.mime_type)
-#        response['Content-Disposition'] = 'attachment; '+'filename='+media.title+'.'+ext
-        return response
-
-    @method_decorator(permission_required('telemeta.change_mediaitem'))
-    def related_media_edit(self, request, public_id, template):
-        item = MediaItem.objects.get(public_id=public_id)
-        MediaItemRelatedFormSet = inlineformset_factory(MediaItem, MediaItemRelated, form=MediaItemRelatedForm)
-        if request.method == 'POST':
-            formset = MediaItemRelatedFormSet(data=request.POST, files=request.FILES, instance=item)
-            if formset.is_valid():
-                formset.save()
-                item.set_revision(request.user)
-                return HttpResponseRedirect('/archives/items/'+public_id)
-        else:
-            formset = MediaItemRelatedFormSet(instance=item)
-
-        return render(request, template, {'item': item, 'formset': formset,})
-
-    @method_decorator(permission_required('telemeta.add_mediaitem'))
-    def item_add(self, request, public_id=None, template='telemeta/mediaitem_add.html'):
-        """Add an item"""
-        if public_id:
-            collection = MediaCollection.objects.get(public_id=public_id)
-            items = MediaItem.objects.filter(collection=collection)
-            code = auto_code(items, collection.code)
-            item = MediaItem(collection=collection, code=code)
-            format, created = Format.objects.get_or_create(item=item)
-        else:
-            item = MediaItem()
-            format = Format()
-
-        if request.method == 'POST':
-            item_form = MediaItemForm(data=request.POST, files=request.FILES, instance=item, prefix='item')
-            format_form = FormatForm(data=request.POST, instance=format, prefix='format')
-            if item_form.is_valid() and format_form.is_valid():
-                item_form.save()
-                item.set_revision(request.user)
-                format.item = item
-                format_form.save()
-                code = item_form.cleaned_data['code']
-                if not code:
-                    code = str(item.id)
-                return HttpResponseRedirect('/archives/items/'+code)
-        else:
-            item_form = MediaItemForm(instance=item, prefix='item')
-            format_form = FormatForm(instance=format, prefix='format')
-
-        forms = [item_form, format_form]
-        hidden_fields = ['item-copied_from_item', 'format-item']
-
-        return render(request, template, {'item': item, 'forms': forms, 'hidden_fields': hidden_fields,})
-
-    @method_decorator(permission_required('telemeta.add_mediaitem'))
-    def item_copy(self, request, public_id, template='telemeta/mediaitem_copy.html'):
-        """Copy a given item"""
-        if request.method == 'POST':
-            source_item = MediaItem.objects.get(public_id=public_id)
-            item = MediaItem()
-            format = Format()
-            item_form = MediaItemForm(data=request.POST, files=request.FILES, instance=item, prefix='item')
-            format_form = FormatForm(data=request.POST, instance=format, prefix='format')
-
-            if item_form.is_valid():
-                item_form.save()
-                code = item_form.cleaned_data['code']
-                if not code:
-                    code = str(item.id)
-                if format_form.is_valid():
-                    format.item = item
-                    format_form.save()
-
-                performances = MediaItemPerformance.objects.filter(media_item=source_item)
-                for performance in performances:
-                    performance.pk = None
-                    performance.id = None
-                    performance.media_item = item
-                    performance.save()
-
-                keywords = MediaItemKeyword.objects.filter(item=source_item)
-                for keyword in keywords:
-                    keyword.pk = None
-                    keyword.id = None
-                    keyword.item = item
-                    keyword.save()
-
-                item.set_revision(request.user)
-                return HttpResponseRedirect('/archives/items/'+code)
-        else:
-            item = MediaItem.objects.get(public_id=public_id)
-            items = MediaItem.objects.filter(collection=item.collection)
-            item.code = auto_code(items, item.collection.code)
-            item.approx_duration = ''
-            item_form = MediaItemForm(instance=item, prefix='item')
-            format, created = Format.objects.get_or_create(item=item)
-            format_form = FormatForm(instance=format, prefix='format')
-            item_form.code = item.code
-            item_form.file = None
-
-        forms = [item_form, format_form]
-        hidden_fields = ['item-copied_from_item', 'format-item']
-
-        return render(request, template, {'item': item, "forms": forms, 'hidden_fields': hidden_fields,})
-
-    @method_decorator(permission_required('telemeta.delete_mediaitem'))
-    def item_delete(self, request, public_id):
-        """Delete a given item"""
-        item = MediaItem.objects.get(public_id=public_id)
-        collection = item.collection
-        item.delete()
-        return HttpResponseRedirect('/archives/collections/'+collection.code)
-
-    def item_analyze(self, item):
-        analyses = MediaItemAnalysis.objects.filter(item=item)
-        mime_type = ''
-
-        if analyses:
-            for analysis in analyses:
-                if not item.approx_duration and analysis.analyzer_id == 'duration':
-                    value = analysis.value
-                    time = value.split(':')
-                    time[2] = time[2].split('.')[0]
-                    time = ':'.join(time)
-                    item.approx_duration = time
-                    item.save()
-                if analysis.analyzer_id == 'mime_type':
-                    mime_type = analysis.value
-        else:
-            analyzers = []
-            analyzers_sub = []
-            graphers_sub = []
-
-            if item.file:
-                decoder  = timeside.decoder.FileDecoder(item.file.path)
-                pipe = decoder
-
-                for analyzer in self.analyzers:
-                    subpipe = analyzer()
-                    analyzers_sub.append(subpipe)
-                    pipe = pipe | subpipe
-
-                try:
-                    sizes = settings.TELEMETA_DEFAULT_GRAPHER_SIZES
-                except:
-                    sizes = ['360x130', ]
-
-                for grapher in self.graphers:
-                    for size in sizes:
-                        width = size.split('x')[0]
-                        height = size.split('x')[1]
-                        image_file = '.'.join([item.public_id, grapher.id(), size.replace('x', '_'), 'png'])
-                        path = self.cache_data.dir + os.sep + image_file
-                        graph = grapher(width = int(width), height = int(height))
-                        graphers_sub.append({'graph' : graph, 'path': path})
-                        pipe = pipe | graph
-
-                pipe.run()
-
-                for grapher in graphers_sub:
-                    grapher['graph'].watermark('timeside', opacity=.6, margin=(5,5))
-                    f = open(grapher['path'], 'w')
-                    grapher['graph'].render(grapher['path'])
-                    f.close()
-
-                mime_type = decoder.format()
-                analysis = MediaItemAnalysis(item=item, name='MIME type',
-                                             analyzer_id='mime_type', unit='', value=mime_type)
-                analysis.save()
-                analysis = MediaItemAnalysis(item=item, name='Channels',
-                                             analyzer_id='channels',
-                                             unit='', value=decoder.channels())
-                analysis.save()
-                analysis = MediaItemAnalysis(item=item, name='Samplerate',
-                                             analyzer_id='samplerate', unit='Hz',
-                                             value=unicode(decoder.audiorate))
-                analysis.save()
-                analysis = MediaItemAnalysis(item=item, name='Resolution',
-                                             analyzer_id='resolution', unit='bits',
-                                             value=unicode(decoder.audiowidth))
-                analysis.save()
-                analysis = MediaItemAnalysis(item=item, name='Duration',
-                                             analyzer_id='duration', unit='s',
-                                             value=unicode(datetime.timedelta(0,decoder.duration)))
-                analysis.save()
-
-                for analyzer in analyzers_sub:
-                    value = analyzer.result()
-                    analysis = MediaItemAnalysis(item=item, name=analyzer.name(),
-                                                 analyzer_id=analyzer.id(),
-                                                 unit=analyzer.unit(), value=str(value))
-                    analysis.save()
-
-#                FIXME: parse tags on first load
-#                tags = decoder.tags
-
-        return mime_type
-
-    def item_analyze_xml(self, request, public_id):
-        item = MediaItem.objects.get(public_id=public_id)
-        analyses = MediaItemAnalysis.objects.filter(item=item)
-        analyzers = []
-        for analysis in analyses:
-            analyzers.append(analysis.to_dict())
-        mime_type = 'text/xml'
-        response = HttpResponse(self.cache_data.get_analyzer_xml(analyzers), mimetype=mime_type)
-        response['Content-Disposition'] = 'attachment; filename='+public_id+'.xml'
-        return response
-
-    def item_visualize(self, request, public_id, visualizer_id, width, height):
-        item = MediaItem.objects.get(public_id=public_id)
-        mime_type = 'image/png'
-        grapher_id = visualizer_id
-
-        for grapher in self.graphers:
-            if grapher.id() == grapher_id:
-                break
-
-        if grapher.id() != grapher_id:
-            raise Http404
-
-        size = width + '_' + height
-        image_file = '.'.join([public_id, grapher_id, size, 'png'])
-
-        if not self.cache_data.exists(image_file):
-            if item.file:
-                path = self.cache_data.dir + os.sep + image_file
-                decoder  = timeside.decoder.FileDecoder(item.file.path)
-                graph = grapher(width = int(width), height = int(height))
-                pipe = decoder | graph
-                pipe.run()
-                graph.watermark('timeside', opacity=.6, margin=(5,5))
-                f = open(path, 'w')
-                graph.render(path)
-                f.close()
-
-        response = HttpResponse(self.cache_data.read_stream_bin(image_file), mimetype=mime_type)
-        return response
-
-    def list_export_extensions(self):
-        "Return the recognized item export file extensions, as a list"
-        list = []
-        for encoder in self.encoders:
-            list.append(encoder.file_extension())
-        #FIXME: MP4
-        list.append('mp4')
-        return list
-
-    def item_export(self, request, public_id, extension):
-        """Export a given media item in the specified format (OGG, FLAC, ...)"""
-
-        item = MediaItem.objects.get(public_id=public_id)
-        public_access = get_public_access(item.public_access,
-                                          str(item.recorded_from_date).split('-')[0],
-                                          str(item.recorded_to_date).split('-')[0])
-
-        if (not public_access or not extension in settings.TELEMETA_STREAMING_FORMATS) and \
-                    not (request.user.has_perm('telemeta.can_play_all_items') or request.user.is_superuser):
-            mess = ugettext('Access not allowed')
-            title = 'Item file : ' + public_id + '.' + extension + ' : ' + mess
-            description = ugettext('Please login or contact the website administator to get a private access.')
-            messages.error(request, title)
-            return render(request, 'telemeta/messages.html', {'description' : description})
-
-        #FIXME: MP4 handling in TimeSide
-        if 'mp4' in extension:
-            mime_type = 'video/mp4'
-            video = item.file.path
-            response = HttpResponse(stream_from_file(video), mimetype = mime_type)
-            response['Content-Disposition'] = 'attachment'
-            return response
-
-        if 'webm' in extension:
-            mime_type = 'video/webm'
-            video = item.file.path
-            response = HttpResponse(stream_from_file(video), mimetype = mime_type)
-            response['Content-Disposition'] = 'attachment'
-            return response
-
-        for encoder in self.encoders:
-            if encoder.file_extension() == extension:
-                break
-
-        if encoder.file_extension() != extension:
-            raise Http404('Unknown export file extension: %s' % extension)
-
-        mime_type = encoder.mime_type()
-        file = public_id + '.' + encoder.file_extension()
-        audio = item.file.path
-
-        flag = MediaItemTranscodingFlag.objects.filter(item=item, mime_type=mime_type)
-        if not flag:
-            flag = MediaItemTranscodingFlag(item=item, mime_type=mime_type)
-            flag.value = False
-            flag.save()
-        else:
-            flag = flag[0]
-
-        format = self.item_analyze(item)
-        dc_metadata = dublincore.express_item(item).to_list()
-        mapping = DublinCoreToFormatMetadata(extension)
-        metadata = mapping.get_metadata(dc_metadata)
-
-        if mime_type in format:
-            # source > stream
-            if not extension in mapping.unavailable_extensions:
-                proc = encoder(audio)
-                proc.set_metadata(metadata)
-                try:
-                    proc.write_metadata()
-                except:
-                    pass
-            response = HttpResponse(stream_from_file(audio), mimetype = mime_type)
-#            fsock = open(audio, 'r')
-#            response = HttpResponse(fsock, mimetype = mime_type)
-        else:
-            media = self.cache_export.dir + os.sep + file
-            if not self.cache_export.exists(file) or not flag.value:
-                # source > encoder > stream
-                decoder = timeside.decoder.FileDecoder(audio)
-                decoder.setup()
-                proc = encoder(media, streaming=True)
-                proc.setup(channels=decoder.channels(), samplerate=decoder.samplerate())
-                if extension in mapping.unavailable_extensions:
-                    metadata=None
-                response = HttpResponse(stream_from_processor(decoder, proc, flag, metadata=metadata), mimetype = mime_type)
-            else:
-                # cache > stream
-                response = HttpResponse(self.cache_export.read_stream_bin(file), mimetype = mime_type)
-
-        response['Content-Disposition'] = 'attachment'
-        return response
-
-    def item_playlist(self, request, public_id, template, mimetype):
-        try:
-            item = MediaItem.objects.get(public_id=public_id)
-        except ObjectDoesNotExist:
-            raise Http404
-
-        template = loader.get_template(template)
-        context = RequestContext(request, {'item': item, 'host': request.META['HTTP_HOST']})
-        return HttpResponse(template.render(context), mimetype=mimetype)
-
-    @method_decorator(permission_required('telemeta.change_mediaitem'))
-    def item_performances_edit(self, request, public_id, template):
-        item = MediaItem.objects.get(public_id=public_id)
-        PerformanceFormSet = inlineformset_factory(MediaItem, MediaItemPerformance, form=MediaItemPerformanceForm)
-        if request.method == 'POST':
-            formset = PerformanceFormSet(data=request.POST, instance=item)
-            if formset.is_valid():
-                formset.save()
-                return HttpResponseRedirect('/archives/items/'+public_id)
-        else:
-            formset = PerformanceFormSet(instance=item)
-        return render(request, template, {'item': item, 'formset': formset,})
-
-    @method_decorator(permission_required('telemeta.change_mediaitem'))
-    def item_keywords_edit(self, request, public_id, template):
-        item = MediaItem.objects.get(public_id=public_id)
-        FormSet = inlineformset_factory(MediaItem, MediaItemKeyword)
-        if request.method == 'POST':
-            formset = FormSet(data=request.POST, instance=item)
-            if formset.is_valid():
-                formset.save()
-                return HttpResponseRedirect('/archives/items/'+public_id)
-        else:
-            formset = FormSet(instance=item)
-        return render(request, template, {'item': item, 'formset': formset,})
-
-
-class AdminView(object):
-    """Provide Admin web UI methods"""
-
-    @method_decorator(permission_required('is_superuser'))
-    def admin_index(self, request):
-        return render(request, 'telemeta/admin.html', self.__get_admin_context_vars())
-
-    @method_decorator(permission_required('is_superuser'))
-    def admin_general(self, request):
-        return render(request, 'telemeta/admin_general.html', self.__get_admin_context_vars())
-
-    @method_decorator(permission_required('is_superuser'))
-    def admin_enumerations(self, request):
-        return render(request, 'telemeta/admin_enumerations.html', self.__get_admin_context_vars())
-
-    @method_decorator(permission_required('is_superuser'))
-    def admin_users(self, request):
-        users = User.objects.all()
-        return render(request, 'telemeta/admin_users.html', {'users': users})
-
-    def __get_enumerations_list(self):
-        from django.db.models import get_models
-        models = get_models(telemeta.models)
-
-        enumerations = []
-        for model in models:
-            if issubclass(model, Enumeration):
-                enumerations.append({"name": model._meta.verbose_name,
-                    "id": model._meta.module_name})
-
-        cmp = lambda obj1, obj2: unaccent_icmp(obj1['name'], obj2['name'])
-        enumerations.sort(cmp)
-        return enumerations
-
-    def __get_admin_context_vars(self):
-        return {"enumerations": self.__get_enumerations_list()}
-
-    def __get_enumeration(self, id):
-        from django.db.models import get_models
-        models = get_models(telemeta.models)
-        for model in models:
-            if model._meta.module_name == id:
-                break
-
-        if model._meta.module_name != id:
-            return None
-
-        return model
-
-    @method_decorator(permission_required('telemeta.change_keyword'))
-    def edit_enumeration(self, request, enumeration_id):
-
-        enumeration  = self.__get_enumeration(enumeration_id)
-        if enumeration == None:
-            raise Http404
-
-        vars = self.__get_admin_context_vars()
-        vars["enumeration_id"] = enumeration._meta.module_name
-        vars["enumeration_name"] = enumeration._meta.verbose_name
-        vars["enumeration_values"] = enumeration.objects.all()
-        return render(request, 'telemeta/enumeration_edit.html', vars)
-
-    @method_decorator(permission_required('telemeta.add_keyword'))
-    def add_to_enumeration(self, request, enumeration_id):
-
-        enumeration  = self.__get_enumeration(enumeration_id)
-        if enumeration == None:
-            raise Http404
-
-        enumeration_value = enumeration(value=request.POST['value'])
-        enumeration_value.save()
-
-        return self.edit_enumeration(request, enumeration_id)
-
-    @method_decorator(permission_required('telemeta.change_keyword'))
-    def update_enumeration(self, request, enumeration_id):
-
-        enumeration  = self.__get_enumeration(enumeration_id)
-        if enumeration == None:
-            raise Http404
-
-        if request.method == 'POST':
-            enumeration.objects.filter(id__in=request.POST.getlist('sel')).delete()
-
-        return self.edit_enumeration(request, enumeration_id)
-
-    @method_decorator(permission_required('telemeta.change_keyword'))
-    def edit_enumeration_value(self, request, enumeration_id, value_id):
-
-        enumeration  = self.__get_enumeration(enumeration_id)
-        if enumeration == None:
-            raise Http404
-
-        vars = self.__get_admin_context_vars()
-        vars["enumeration_id"] = enumeration._meta.module_name
-        vars["enumeration_name"] = enumeration._meta.verbose_name
-        vars["enumeration_record"] = enumeration.objects.get(id__exact=value_id)
-        return render(request, 'telemeta/enumeration_edit_value.html', vars)
-
-    @method_decorator(permission_required('telemeta.change_keyword'))
-    def update_enumeration_value(self, request, enumeration_id, value_id):
-
-        if request.method == 'POST':
-            enumeration  = self.__get_enumeration(enumeration_id)
-            if enumeration == None:
-                raise Http404
-
-            record = enumeration.objects.get(id__exact=value_id)
-            record.value = request.POST["value"]
-            record.save()
-
-        return self.edit_enumeration(request, enumeration_id)
-
-
-class InstrumentView(object):
-    """Provide Instrument web UI methods"""
-
-    @method_decorator(permission_required('telemeta.change_instrument'))
-    def edit_instrument(self, request):
-
-        instruments = Instrument.objects.all().order_by('name')
-        if instruments == None:
-            raise Http404
-        return render(request, 'telemeta/instrument_edit.html', {'instruments': instruments})
-
-    @method_decorator(permission_required('telemeta.add_instrument'))
-    def add_to_instrument(self, request):
-
-        if request.method == 'POST':
-            instrument = Instrument(name=request.POST['value'])
-            instrument.save()
-
-        return self.edit_instrument(request)
-
-    @method_decorator(permission_required('telemeta.change_instrument'))
-    def update_instrument(self, request):
-
-        if request.method == 'POST':
-            Instrument.objects.filter(id__in=request.POST.getlist('sel')).delete()
-
-        return self.edit_instrument(request)
-
-    @method_decorator(permission_required('telemeta.change_instrument'))
-    def edit_instrument_value(self, request, value_id):
-        instrument = Instrument.objects.get(id__exact=value_id)
-
-        return render(request, 'telemeta/instrument_edit_value.html', {'instrument': instrument})
-
-    @method_decorator(permission_required('telemeta.change_instrument'))
-    def update_instrument_value(self, request, value_id):
-
-        if request.method == 'POST':
-            instrument = Instrument.objects.get(id__exact=value_id)
-            instrument.name = request.POST["value"]
-            instrument.save()
-
-        return self.edit_instrument(request)
-
-
-class GeoView(object):
-    """Provide Geo web UI methods"""
-
-    def list_continents(self, request):
-        continents = MediaItem.objects.all().countries(group_by_continent=True)
-        return render(request, 'telemeta/geo_continents.html',
-                    {'continents': continents, 'gmap_key': settings.TELEMETA_GMAP_KEY })
-
-    def country_info(self, request, id):
-        country = Location.objects.get(pk=id)
-        return render(request, 'telemeta/country_info.html', {
-            'country': country, 'continent': country.continents()[0]})
-
-    def list_countries(self, request, continent):
-        continent = Location.objects.by_flatname(continent)[0]
-        countries = MediaItem.objects.by_location(continent).countries()
-
-        return render(request, 'telemeta/geo_countries.html', {
-            'continent': continent,
-            'countries': countries
-        })
-
-    def list_country_collections(self, request, continent, country):
-        continent = Location.objects.by_flatname(continent)[0]
-        country = Location.objects.by_flatname(country)[0]
-        objects = MediaCollection.objects.enriched().by_location(country)
-        return list_detail.object_list(request, objects,
-            template_name='telemeta/geo_country_collections.html', paginate_by=20,
-            extra_context={'country': country, 'continent': continent})
-
-    def list_country_items(self, request, continent, country):
-        continent = Location.objects.by_flatname(continent)[0]
-        country = Location.objects.by_flatname(country)[0]
-        objects = MediaItem.objects.enriched().by_location(country)
-        return list_detail.object_list(request, objects,
-            template_name='telemeta/geo_country_items.html', paginate_by=20,
-            extra_context={'country': country, 'continent': continent})
-
-class MarkerView(object):
-    """Provide Collections web UI methods"""
-
-    @jsonrpc_method('telemeta.add_marker')
-    def add_marker(request, marker):
-        # marker must be a dict
-        if isinstance(marker, dict):
-            item_id = marker['item_id']
-            item = MediaItem.objects.get(id=item_id)
-            m = MediaItemMarker(item=item)
-            m.public_id = marker['public_id']
-            m.time = float(marker['time'])
-            m.title = marker['title']
-            m.description = marker['description']
-            m.author = User.objects.get(username=marker['author'])
-            m.save()
-            m.set_revision(request.user)
-        else:
-            raise 'Error : Bad marker dictionnary'
-
-    @jsonrpc_method('telemeta.del_marker')
-    def del_marker(request, public_id):
-        m = MediaItemMarker.objects.get(public_id=public_id)
-        m.delete()
-
-    @jsonrpc_method('telemeta.get_markers')
-    def get_markers(request, item_id):
-        item = MediaItem.objects.get(id=item_id)
-        markers = MediaItemMarker.objects.filter(item=item)
-        list = []
-        for marker in markers:
-            dict = {}
-            dict['public_id'] = marker.public_id
-            dict['time'] = str(marker.time)
-            dict['title'] = marker.title
-            dict['description'] = marker.description
-            dict['author'] = marker.author.username
-            list.append(dict)
-        return list
-
-    @jsonrpc_method('telemeta.update_marker')
-    def update_marker(request, marker):
-        if isinstance(marker, dict):
-            m = MediaItemMarker.objects.get(public_id=marker['public_id'])
-            m.time = float(marker['time'])
-            m.title = marker['title']
-            m.description = marker['description']
-            m.save()
-            m.set_revision(request.user)
-        else:
-            raise 'Error : Bad marker dictionnary'
-
-    @jsonrpc_method('telemeta.get_marker_id')
-    def get_marker_id(request, public_id):
-        marker = MediaItemMarker.objects.get(public_id=public_id)
-        return marker.id
-
-class PlaylistView(object):
-    """Provide Playlist web UI methods"""
-
-    @jsonrpc_method('telemeta.add_playlist')
-    def add_playlist(request, playlist):
-        # playlist must be a dict
-        if isinstance(playlist, dict):
-            m = Playlist()
-            m.public_id = playlist['public_id']
-            m.title = playlist['title']
-            m.description = playlist['description']
-            m.author = request.user
-            m.save()
-        else:
-            raise 'Error : Bad playlist dictionnary'
-
-    @jsonrpc_method('telemeta.del_playlist')
-    def del_playlist(request, public_id):
-        m = Playlist.objects.get(public_id=public_id)
-        m.delete()
-
-    @jsonrpc_method('telemeta.update_playlist')
-    def update_playlist(request, playlist):
-        if isinstance(playlist, dict):
-            m = Playlist.objects.get(public_id=playlist['public_id'])
-            m.title = playlist['title']
-            m.description = playlist['description']
-            m.save()
-        else:
-            raise 'Error : Bad playlist dictionnary'
-
-    @jsonrpc_method('telemeta.add_playlist_resource')
-    def add_playlist_resource(request, playlist_id, playlist_resource):
-        # playlist_resource must be a dict
-        if isinstance(playlist_resource, dict):
-            m = PlaylistResource()
-            m.public_id = playlist_resource['public_id']
-            m.playlist = Playlist.objects.get(public_id=playlist_id, author=request.user)
-            m.resource_type = playlist_resource['resource_type']
-            m.resource_id = playlist_resource['resource_id']
-            m.save()
-        else:
-            raise 'Error : Bad playlist_resource dictionnary'
-
-    @jsonrpc_method('telemeta.del_playlist_resource')
-    def del_playlist_resource(request, public_id):
-        m = PlaylistResource.objects.get(public_id=public_id)
-        m.delete()
-
-
-    def playlist_csv_export(self, request, public_id, resource_type):
-        playlist = Playlist.objects.get(public_id=public_id, author=request.user)
-        resources = PlaylistResource.objects.filter(playlist=playlist)
-        response = HttpResponse(mimetype='text/csv')
-        response['Content-Disposition'] = 'attachment; filename='+playlist.title+'_'+resource_type+'.csv'
-        writer = UnicodeWriter(response)
-
-        elements = []
-        for resource in resources:
-            if resource_type == 'items':
-                if resource.resource_type == 'collection':
-                    collection = MediaCollection.objects.get(id=resource.resource_id)
-                    collection_items = MediaItem.objects.filter(collection=collection)
-                    for item in collection_items:
-                        elements.append(item)
-                elif resource.resource_type == 'item':
-                    item = MediaItem.objects.get(id=resource.resource_id)
-                    elements.append(item)
-
-            elif resource_type == 'collections':
-                if resource.resource_type == 'collection':
-                    collection = MediaCollection.objects.get(id=resource.resource_id)
-                    elements.append(collection)
-
-        if elements:
-            element = elements[0].to_dict()
-            tags = element.keys()
-            # code and title on the two first column
-            tags.remove('code')
-            tags.remove('title')
-            tags.sort()
-            tags.insert(0, 'title')
-            tags.insert(0, 'code')
-            writer.writerow(tags)
-
-            for element in elements:
-                data = []
-                element = element.to_dict()
-                for tag in tags:
-                    data.append(element[tag])
-                writer.writerow(data)
-        return response
-
-
-class ProfileView(object):
-    """Provide Collections web UI methods"""
-
-    @method_decorator(login_required)
-    def profile_detail(self, request, username, template='telemeta/profile_detail.html'):
-        user = User.objects.get(username=username)
-        try:
-            profile = user.get_profile()
-        except:
-            profile = None
-        playlists = get_playlists(request, user)
-        user_revisions = get_revisions(25, user)
-
-        return render(request, template, {'profile' : profile, 'usr': user, 'playlists': playlists,
-                                          'user_revisions': user_revisions})
-
-    @method_decorator(login_required)
-    def profile_edit(self, request, username, template='telemeta/profile_edit.html'):
-        if request.user.is_superuser:
-            user_hidden_fields = ['profile-user', 'user-password', 'user-last_login', 'user-date_joined']
-        else:
-            user_hidden_fields = ['user-username', 'user-is_staff', 'profile-user', 'user-is_active',
-                         'user-password', 'user-last_login', 'user-date_joined', 'user-groups',
-                         'user-user_permissions', 'user-is_superuser', 'profile-expiration_date']
-
-        user = User.objects.get(username=username)
-        if user != request.user and not request.user.is_staff:
-            mess = ugettext('Access not allowed')
-            title = ugettext('User profile') + ' : ' + username + ' : ' + mess
-            description = ugettext('Please login or contact the website administator to get a private access.')
-            messages.error(request, title)
-            return render(request, 'telemeta/messages.html', {'description' : description})
-
-        try:
-            profile = user.get_profile()
-        except:
-            profile = UserProfile(user=user)
-
-        if request.method == 'POST':
-            user_form = UserChangeForm(request.POST, instance=user, prefix='user')
-            profile_form = UserProfileForm(request.POST, instance=profile, prefix='profile')
-            if user_form.is_valid() and profile_form.is_valid():
-                user_form.save()
-                profile_form.save()
-                return HttpResponseRedirect('/accounts/'+username+'/profile/')
-        else:
-            user_form = UserChangeForm(instance=user, prefix='user')
-            profile_form = UserProfileForm(instance=profile, prefix='profile')
-            forms = [user_form, profile_form]
-        return render(request, template, {'forms': forms, 'usr': user,
-                                'user_hidden_fields': user_hidden_fields})
-
-
-class LastestRevisionsFeed(Feed):
-    "the RSS feed of the lastest revisions"
-
-    organization = settings.TELEMETA_ORGANIZATION
-    subjects = settings.TELEMETA_SUBJECTS
-    tags = ['title', 'description', 'comment']
-    title = organization.decode('utf8') + ' - Telemeta - ' + ugettext('Last changes')
-    link = ""
-    description = ' '.join([subject.decode('utf-8') for subject in subjects])
-    n_items = 100
-
-    def items(self):
-        return get_revisions(self.n_items)
-
-    def item_title(self, r):
-        element = r['element']
-        if element.title == '':
-            title = str(element.public_id)
-        else:
-            title = element.title
-        return element.element_type + ' : ' + title
-
-    def item_description(self, r):
-        revision = r['revision']
-        element = r['element']
-        description = '<b>modified by ' + revision.user.username + ' on ' + unicode(revision.time) + '</b><br /><br />'
-        dict = element.to_dict()
-        for tag in dict.keys():
-            try:
-                value = dict[tag]
-                if value != '':
-                    description += tag + ' : ' + value + '<br />'
-            except:
-                continue
-        return description.encode('utf-8')
-
-    def item_link(self, r):
-        revision = r['revision']
-        element = r['element']
-        if revision.element_type[-1] == 's':
-            dir = revision.element_type
-        else:
-            dir = revision.element_type + 's'
-        link = '/archives/' + dir + '/' + str(element.public_id)
-        return link
-
-
-class UserRevisionsFeed(LastestRevisionsFeed):
-
-    def get_object(self, request, username):
-        return get_object_or_404(User, username=username)
-
-    def items(self, obj):
-        return get_revisions(self.n_items, obj)
-
-
-class ResourceView(object):
-    """Provide Resource web UI methods"""
-
-    types = {'corpus':
-                {'model': MediaCorpus,
-                'form' : MediaCorpusForm,
-                'related': MediaCorpusRelated,
-                'related_form': MediaCorpusRelatedForm,
-                'parent': MediaFonds,
-                },
-            'fonds':
-                {'model': MediaFonds,
-                'form' : MediaFondsForm,
-                'related': MediaFondsRelated,
-                'related_form': MediaFondsRelatedForm,
-                'parent': None,
-                }
-            }
-
-    def setup(self, type):
-        self.model = self.types[type]['model']
-        self.form = self.types[type]['form']
-        self.related = self.types[type]['related']
-        self.related_form = self.types[type]['related_form']
-        self.parent = self.types[type]['parent']
-        self.type = type
-
-    def detail(self, request, type, public_id, template='telemeta/resource_detail.html'):
-        self.setup(type)
-        resource = self.model.objects.get(code=public_id)
-        children = resource.children.all()
-        children = children.order_by('code')
-        related_media = self.related.objects.filter(resource=resource)
-        check_related_media(related_media)
-        playlists = get_playlists(request)
-        revisions = Revision.objects.filter(element_type=type, element_id=resource.id).order_by('-time')
-        if revisions:
-            last_revision = revisions[0]
-        else:
-            last_revision = None
-        if self.parent:
-            parents = self.parent.objects.filter(children=resource)
-        else:
-            parents = []
-
-        return render(request, template, {'resource': resource, 'type': type, 'children': children,
-                        'related_media': related_media, 'parents': parents, 'playlists': playlists,
-                        'last_revision': last_revision })
-
-    @jsonrpc_method('telemeta.change_fonds')
-    @jsonrpc_method('telemeta.change_corpus')
-    def edit(self, request, type, public_id, template='telemeta/resource_edit.html'):
-        self.setup(type)
-        resource = self.model.objects.get(code=public_id)
-        if request.method == 'POST':
-            form = self.form(data=request.POST, files=request.FILES, instance=resource)
-            if form.is_valid():
-                code = form.cleaned_data['code']
-                if not code:
-                    code = public_id
-                form.save()
-                resource.set_revision(request.user)
-                return HttpResponseRedirect('/archives/'+self.type+'/'+code)
-        else:
-            form = self.form(instance=resource)
-        return render(request, template, {'resource': resource, 'type': type, 'form': form,})
-
-    @jsonrpc_method('telemeta.add_fonds')
-    @jsonrpc_method('telemeta.add_corpus')
-    def add(self, request, type, template='telemeta/resource_add.html'):
-        self.setup(type)
-        resource = self.model()
-        if request.method == 'POST':
-            form = self.form(data=request.POST, files=request.FILES, instance=resource)
-            if form.is_valid():
-                code = form.cleaned_data['code']
-                if not code:
-                    code = public_id
-                form.save()
-                resource.set_revision(request.user)
-                return HttpResponseRedirect('/archives/'+self.type +'/'+code)
-        else:
-            form = self.form(instance=resource)
-        return render(request, template, {'resource': resource, 'type': type, 'form': form,})
-
-    @jsonrpc_method('telemeta.add_fonds')
-    @jsonrpc_method('telemeta.add_corpus')
-    def copy(self, request, type, public_id, template='telemeta/resource_edit.html'):
-        self.setup(type)
-        if request.method == 'POST':
-            resource = self.model()
-            form = self.form(data=request.POST, files=request.FILES, instance=resource)
-            if form.is_valid():
-                code = form.cleaned_data['code']
-                if not code:
-                    code = public_id
-                resource.save()
-                resource.set_revision(request.user)
-                return HttpResponseRedirect('/archives/'+self.type +'/'+code)
-        else:
-            resource = self.model.objects.get(code=public_id)
-            form = self.form(instance=resource)
-        return render(request, template, {'resource': resource, 'type': type, "form": form,})
-
-    def playlist(self, request, type, public_id, template, mimetype):
-        self.setup(type)
-        try:
-            resource = self.model.objects.get(code=public_id)
-        except ObjectDoesNotExist:
-            raise Http404
-
-        template = loader.get_template(template)
-        context = RequestContext(request, {'resource': resource, 'host': request.META['HTTP_HOST']})
-        return HttpResponse(template.render(context), mimetype=mimetype)
-
-    @jsonrpc_method('telemeta.del_fonds')
-    @jsonrpc_method('telemeta.del_corpus')
-    def delete(self, request, type, public_id):
-        self.setup(type)
-        resource = self.model.objects.get(code=public_id)
-        resource.delete()
-        return HttpResponseRedirect('/archives/'+self.type+'/')
-
-    def related_stream(self, request, type, public_id, media_id):
-        self.setup(type)
-        resource = self.model.objects.get(code=public_id)
-        media = self.related.objects.get(resource=resource, id=media_id)
-        response = HttpResponse(stream_from_file(media.file.path), mimetype=media.mime_type)
-        return response
-
-    @jsonrpc_method('telemeta.add_fonds_related_media')
-    @jsonrpc_method('telemeta.add_corpus_related_media')
-    def related_edit(self, request, type, public_id, template):
-        self.setup(type)
-        resource = self.model.objects.get(code=public_id)
-        ResourceRelatedFormSet = inlineformset_factory(self.model, self.related, form=self.related_form)
-        if request.method == 'POST':
-            formset = ResourceRelatedFormSet(data=request.POST, files=request.FILES, instance=resource)
-            if formset.is_valid():
-                formset.save()
-                resource.set_revision(request.user)
-                return HttpResponseRedirect('/archives/'+self.type+'/'+public_id)
-        else:
-            formset = ResourceRelatedFormSet(instance=resource)
-        return render(request, template, {'resource': resource, 'type': type, 'formset': formset,})
-
-
diff --git a/telemeta/views/collection.py b/telemeta/views/collection.py
new file mode 100644 (file)
index 0000000..de2677f
--- /dev/null
@@ -0,0 +1,162 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2007-2010 Samalyse SARL
+# Copyright (C) 2010-2012 Parisson SARL
+
+# This software is a computer program whose purpose is to backup, analyse,
+# transcode and stream any audio content with its metadata over a web frontend.
+
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+
+# Authors: Olivier Guilyardi <olivier@samalyse.com>
+#          Guillaume Pellerin <yomguy@parisson.com>
+
+
+from telemeta.views.core import *
+
+class CollectionView(object):
+    """Provide Collections web UI methods"""
+
+    def collection_detail(self, request, public_id, template='telemeta/collection_detail.html'):
+        collection = MediaCollection.objects.get(public_id=public_id)
+        items = collection.items.enriched()
+        items = items.order_by('code', 'old_code')
+
+        if collection.public_access == 'none' and not (request.user.is_staff or request.user.is_superuser):
+            mess = ugettext('Access not allowed')
+            title = ugettext('Collection') + ' : ' + public_id + ' : ' + mess
+            description = ugettext('Please login or contact the website administator to get a private access.')
+            messages.error(request, title)
+            return render(request, 'telemeta/messages.html', {'description' : description})
+
+        public_access = get_public_access(collection.public_access, collection.recorded_from_year,
+                                                collection.recorded_to_year)
+        playlists = get_playlists(request)
+
+        related_media = MediaCollectionRelated.objects.filter(collection=collection)
+        check_related_media(related_media)
+        parents = MediaCorpus.objects.filter(children=collection)
+        revisions = Revision.objects.filter(element_type='collection',
+                                            element_id=collection.id).order_by('-time')
+        if revisions:
+            last_revision = revisions[0]
+        else:
+            last_revision = None
+
+        return render(request, template, {'collection': collection, 'playlists': playlists,
+                'public_access': public_access, 'items': items, 'related_media': related_media,
+                'parents': parents, 'last_revision': last_revision })
+
+    @method_decorator(permission_required('telemeta.change_mediacollection'))
+    def collection_edit(self, request, public_id, template='telemeta/collection_edit.html'):
+        collection = MediaCollection.objects.get(public_id=public_id)
+        if request.method == 'POST':
+            form = MediaCollectionForm(data=request.POST, files=request.FILES, instance=collection)
+            if form.is_valid():
+                code = form.cleaned_data['code']
+                if not code:
+                    code = public_id
+                form.save()
+                collection.set_revision(request.user)
+                return HttpResponseRedirect('/archives/collections/'+code)
+        else:
+            form = MediaCollectionForm(instance=collection)
+
+        return render(request, template, {'collection': collection, "form": form,})
+
+    @method_decorator(permission_required('telemeta.add_mediacollection'))
+    def collection_add(self, request, template='telemeta/collection_add.html'):
+        collection = MediaCollection()
+        if request.method == 'POST':
+            form = MediaCollectionForm(data=request.POST, files=request.FILES, instance=collection)
+            if form.is_valid():
+                code = form.cleaned_data['code']
+                if not code:
+                    code = public_id
+                form.save()
+                collection.set_revision(request.user)
+                return HttpResponseRedirect('/archives/collections/'+code)
+        else:
+            form = MediaCollectionForm(instance=collection)
+
+        return render(request, template, {'collection': collection, "form": form,})
+
+    @method_decorator(permission_required('telemeta.add_mediacollection'))
+    def collection_copy(self, request, public_id, template='telemeta/collection_edit.html'):
+        if request.method == 'POST':
+            collection = MediaCollection()
+            form = MediaCollectionForm(data=request.POST, files=request.FILES, instance=collection)
+            if form.is_valid():
+                code = form.cleaned_data['code']
+                if not code:
+                    code = public_id
+                form.save()
+                collection.set_revision(request.user)
+                return HttpResponseRedirect('/archives/collections/'+code)
+        else:
+            collection = MediaCollection.objects.get(public_id=public_id)
+            form = MediaCollectionForm(instance=collection)
+
+        return render(request, template, {'collection': collection, "form": form,})
+
+    def collection_playlist(self, request, public_id, template, mimetype):
+        try:
+            collection = MediaCollection.objects.get(public_id=public_id)
+        except ObjectDoesNotExist:
+            raise Http404
+
+        template = loader.get_template(template)
+        context = RequestContext(request, {'collection': collection, 'host': request.META['HTTP_HOST']})
+        return HttpResponse(template.render(context), mimetype=mimetype)
+
+    @method_decorator(permission_required('telemeta.delete_mediacollection'))
+    def collection_delete(self, request, public_id):
+        """Delete a given collection"""
+        collection = MediaCollection.objects.get(public_id=public_id)
+        collection.delete()
+        return HttpResponseRedirect('/archives/collections/')
+
+    def related_media_collection_stream(self, request, collection_public_id, media_id):
+        collection = MediaCollection.objects.get(public_id=collection_public_id)
+        media = MediaCollectionRelated.objects.get(collection=collection, id=media_id)
+        response = HttpResponse(stream_from_file(media.file.path), mimetype=media.mime_type)
+#        response['Content-Disposition'] = 'attachment'
+        return response
+
+    @method_decorator(permission_required('telemeta.change_mediacollection'))
+    def related_media_edit(self, request, public_id, template):
+        collection = MediaCollection.objects.get(public_id=public_id)
+        MediaCollectionRelatedFormSet = inlineformset_factory(MediaCollection, MediaCollectionRelated, form=MediaCollectionRelatedForm)
+        if request.method == 'POST':
+            formset = MediaCollectionRelatedFormSet(data=request.POST, files=request.FILES, instance=collection)
+            if formset.is_valid():
+                formset.save()
+                collection.set_revision(request.user)
+                return HttpResponseRedirect('/archives/collections/'+public_id)
+        else:
+            formset = MediaCollectionRelatedFormSet(instance=collection)
+
+        return render(request, template, {'collection': collection, 'formset': formset,})
diff --git a/telemeta/views/core.py b/telemeta/views/core.py
new file mode 100644 (file)
index 0000000..922a4f4
--- /dev/null
@@ -0,0 +1,216 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2007-2010 Samalyse SARL
+# Copyright (C) 2010-2012 Parisson SARL
+
+# This software is a computer program whose purpose is to backup, analyse,
+# transcode and stream any audio content with its metadata over a web frontend.
+
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+
+# Authors: Olivier Guilyardi <olivier@samalyse.com>
+#          Guillaume Pellerin <yomguy@parisson.com>
+
+import re
+import os
+import sys
+import csv
+import time
+import random
+import datetime
+import timeside
+
+from jsonrpc import jsonrpc_method
+
+from django.utils.decorators import method_decorator
+from django.contrib.auth import authenticate, login
+from django.template import RequestContext, loader
+from django import template
+from django.http import HttpResponse, HttpResponseRedirect
+from django.http import Http404
+from django.shortcuts import render_to_response, redirect, get_object_or_404
+from django.views.generic import list_detail
+from django.views.generic import DetailView
+from django.conf import settings
+from django.contrib import auth
+from django.contrib import messages
+from django.contrib.auth.decorators import login_required, permission_required
+from django.core.context_processors import csrf
+from django.forms.models import modelformset_factory, inlineformset_factory
+from django.contrib.auth.models import User
+from django.utils.translation import ugettext
+from django.contrib.auth.forms import UserChangeForm
+from django.core.exceptions import ObjectDoesNotExist
+from django.contrib.syndication.views import Feed
+from django.core.servers.basehttp import FileWrapper
+
+from telemeta.models import *
+import telemeta.models
+import telemeta.interop.oai as oai
+from telemeta.interop.oaidatasource import TelemetaOAIDataSource
+from telemeta.util.unaccent import unaccent
+from telemeta.util.unaccent import unaccent_icmp
+from telemeta.util.logger import Logger
+from telemeta.util.unicode import UnicodeWriter
+from telemeta.cache import TelemetaCache
+import pages
+from telemeta.forms import *
+
+# Model type definition
+mods = {'item': MediaItem, 'collection': MediaCollection,
+        'corpus': MediaCorpus, 'fonds': MediaFonds, 'marker': MediaItemMarker, }
+
+# TOOLS
+
+class FixedFileWrapper(FileWrapper):
+    def __iter__(self):
+        self.filelike.seek(0)
+        return self
+
+def send_file(request, filename, content_type='image/jpeg'):
+    """
+    Send a file through Django without loading the whole file into
+    memory at once. The FileWrapper will turn the file object into an
+    iterator for chunks of 8KB.
+    """
+    wrapper = FixedFileWrapper(file(filename, 'rb'))
+    response = HttpResponse(wrapper, content_type=content_type)
+    response['Content-Length'] = os.path.getsize(filename)
+    return response
+
+def render(request, template, data = None, mimetype = None):
+    return render_to_response(template, data, context_instance=RequestContext(request),
+                              mimetype=mimetype)
+
+def stream_from_processor(__decoder, __processor, __flag, metadata=None):
+    while True:
+        __frames, __eodproc = __processor.process(*__decoder.process())
+        if __eodproc or not len(__frames):
+            if metadata:
+                __processor.set_metadata(metadata)
+                __processor.write_metadata()
+            __flag.value = True
+            __flag.save()
+            break
+        yield __processor.chunk
+
+def stream_from_file(__file):
+    chunk_size = 0x100000
+    f = open(__file, 'r')
+    while True:
+        __chunk = f.read(chunk_size)
+        if not len(__chunk):
+            f.close()
+            break
+        yield __chunk
+
+def get_public_access(access, year_from=None, year_to=None):
+    # Rolling publishing date : public access is given when time between recorded year
+    # and current year is over the settings value PUBLIC_ACCESS_PERIOD
+    if year_from and not year_from == 0:
+        year = year_from
+    elif year_to and not year_to == 0:
+        year = year_to
+    else:
+        year = 0
+    if access == 'full':
+        public_access = True
+    else:
+        public_access = False
+        if year and not year == 'None':
+            year_now = datetime.datetime.now().strftime("%Y")
+            if int(year_now) - int(year) >= settings.TELEMETA_PUBLIC_ACCESS_PERIOD:
+                public_access = True
+        else:
+            public_access = False
+    return public_access
+
+def get_revisions(nb, user=None):
+    last_revisions = Revision.objects.order_by('-time')
+    if user:
+        last_revisions = last_revisions.filter(user=user)
+    last_revisions = last_revisions[0:nb]
+    revisions = []
+
+    for revision in last_revisions:
+        for type in mods.keys():
+            if revision.element_type == type:
+                try:
+                    element = mods[type].objects.get(pk=revision.element_id)
+                except:
+                    element = None
+        if not element == None:
+            revisions.append({'revision': revision, 'element': element})
+    return revisions
+
+def get_playlists(request, user=None):
+    if not user:
+        user = request.user
+    playlists = []
+    if user.is_authenticated():
+        user_playlists = Playlist.objects.filter(author=user)
+        for playlist in user_playlists:
+            playlist_resources = PlaylistResource.objects.filter(playlist=playlist)
+            resources = []
+            for resource in playlist_resources:
+                try:
+                    for type in mods.keys():
+                        if resource.resource_type == type:
+                            element = mods[type].objects.get(id=resource.resource_id)
+                except:
+                    element = None
+                resources.append({'element': element, 'type': resource.resource_type, 'public_id': resource.public_id })
+            playlists.append({'playlist': playlist, 'resources': resources})
+    return playlists
+
+def check_related_media(medias):
+    for media in medias:
+        if not media.mime_type:
+            media.set_mime_type()
+            media.save()
+        if not media.title and media.url:
+            if 'https' in media.url:
+                media.url = media.url.replace('https', 'http')
+            import lxml.etree
+            parser = lxml.etree.HTMLParser()
+            tree = lxml.etree.parse(media.url, parser)
+            try:
+                title = tree.find(".//title").text
+            except:
+                title = media.url
+            media.title = title.replace('\n', '').strip()
+            media.save()
+
+def auto_code(resources, base_code):
+    index = 1
+    while True:
+        code = base_code + '_' + str(index)
+        r = resources.filter(code=code)
+        if not r:
+            break
+        index += 1
+    return code
+
diff --git a/telemeta/views/feed.py b/telemeta/views/feed.py
new file mode 100644 (file)
index 0000000..def5dc7
--- /dev/null
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2010-2012 Parisson SARL
+
+# This software is a computer program whose purpose is to backup, analyse,
+# transcode and stream any audio content with its metadata over a web frontend.
+
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+
+# Authors: Guillaume Pellerin <yomguy@parisson.com>
+
+
+from telemeta.views.core import *
+
+
+class LastestRevisionsFeed(Feed):
+    "the RSS feed of the lastest revisions"
+
+    organization = settings.TELEMETA_ORGANIZATION
+    subjects = settings.TELEMETA_SUBJECTS
+    tags = ['title', 'description', 'comment']
+    title = organization.decode('utf8') + ' - Telemeta - ' + ugettext('Last changes')
+    link = ""
+    description = ' '.join([subject.decode('utf-8') for subject in subjects])
+    n_items = 100
+
+    def items(self):
+        return get_revisions(self.n_items)
+
+    def item_title(self, r):
+        element = r['element']
+        if element.title == '':
+            title = str(element.public_id)
+        else:
+            title = element.title
+        return element.element_type + ' : ' + title
+
+    def item_description(self, r):
+        revision = r['revision']
+        element = r['element']
+        description = '<b>modified by ' + revision.user.username + ' on ' + unicode(revision.time) + '</b><br /><br />'
+        dict = element.to_dict()
+        for tag in dict.keys():
+            try:
+                value = dict[tag]
+                if value != '':
+                    description += tag + ' : ' + value + '<br />'
+            except:
+                continue
+        return description.encode('utf-8')
+
+    def item_link(self, r):
+        revision = r['revision']
+        element = r['element']
+        if revision.element_type[-1] == 's':
+            dir = revision.element_type
+        else:
+            dir = revision.element_type + 's'
+        link = '/archives/' + dir + '/' + str(element.public_id)
+        return link
+
+
+class UserRevisionsFeed(LastestRevisionsFeed):
+
+    def get_object(self, request, username):
+        return get_object_or_404(User, username=username)
+
+    def items(self, obj):
+        return get_revisions(self.n_items, obj)
+
diff --git a/telemeta/views/geo.py b/telemeta/views/geo.py
new file mode 100644 (file)
index 0000000..fa4adc1
--- /dev/null
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2007-2010 Samalyse SARL
+# Copyright (C) 2010-2012 Parisson SARL
+
+# This software is a computer program whose purpose is to backup, analyse,
+# transcode and stream any audio content with its metadata over a web frontend.
+
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+
+# Authors: Olivier Guilyardi <olivier@samalyse.com>
+#          Guillaume Pellerin <yomguy@parisson.com>
+
+
+from telemeta.views.core import *
+
+
+class GeoView(object):
+    """Provide Geo web UI methods"""
+
+    def list_continents(self, request):
+        continents = MediaItem.objects.all().countries(group_by_continent=True)
+        return render(request, 'telemeta/geo_continents.html',
+                    {'continents': continents, 'gmap_key': settings.TELEMETA_GMAP_KEY })
+
+    def country_info(self, request, id):
+        country = Location.objects.get(pk=id)
+        return render(request, 'telemeta/country_info.html', {
+            'country': country, 'continent': country.continents()[0]})
+
+    def list_countries(self, request, continent):
+        continent = Location.objects.by_flatname(continent)[0]
+        countries = MediaItem.objects.by_location(continent).countries()
+
+        return render(request, 'telemeta/geo_countries.html', {
+            'continent': continent,
+            'countries': countries
+        })
+
+    def list_country_collections(self, request, continent, country):
+        continent = Location.objects.by_flatname(continent)[0]
+        country = Location.objects.by_flatname(country)[0]
+        objects = MediaCollection.objects.enriched().by_location(country)
+        return list_detail.object_list(request, objects,
+            template_name='telemeta/geo_country_collections.html', paginate_by=20,
+            extra_context={'country': country, 'continent': continent})
+
+    def list_country_items(self, request, continent, country):
+        continent = Location.objects.by_flatname(continent)[0]
+        country = Location.objects.by_flatname(country)[0]
+        objects = MediaItem.objects.enriched().by_location(country)
+        return list_detail.object_list(request, objects,
+            template_name='telemeta/geo_country_items.html', paginate_by=20,
+            extra_context={'country': country, 'continent': continent})
diff --git a/telemeta/views/home.py b/telemeta/views/home.py
new file mode 100644 (file)
index 0000000..ec768ac
--- /dev/null
@@ -0,0 +1,275 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2007-2010 Samalyse SARL
+# Copyright (C) 2010-2012 Parisson SARL
+
+# This software is a computer program whose purpose is to backup, analyse,
+# transcode and stream any audio content with its metadata over a web frontend.
+
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+
+# Authors: Olivier Guilyardi <olivier@samalyse.com>
+#          Guillaume Pellerin <yomguy@parisson.com>
+
+
+from telemeta.views.core import *
+
+class HomeView(object):
+    """Provide general web UI methods"""
+
+    def home(self, request):
+        """Render the index page"""
+
+        template = loader.get_template('telemeta/home.html')
+
+        sound_items = MediaItem.objects.sound()
+        _sound_pub_items = []
+        for item in sound_items:
+            if get_public_access(item.public_access,  str(item.recorded_from_date).split('-')[0],
+                                            str(item.recorded_to_date).split('-')[0]):
+                _sound_pub_items.append(item)
+
+        random.shuffle(_sound_pub_items)
+        if len(_sound_pub_items) != 0:
+            sound_pub_item = _sound_pub_items[0]
+        else:
+            sound_pub_item = None
+        if len(_sound_pub_items) == 2:
+            sound_pub_items = [_sound_pub_items[1]]
+        elif len(_sound_pub_items) > 2:
+            sound_pub_items = _sound_pub_items[1:3]
+        else:
+            sound_pub_items = None
+
+        revisions = get_revisions(25)
+        context = RequestContext(request, {
+                    'page_content': pages.get_page_content(request, 'home', ignore_slash_issue=True),
+                    'revisions': revisions,  'sound_pub_items': sound_pub_items,
+                    'sound_pub_item': sound_pub_item })
+        return HttpResponse(template.render(context))
+
+    def lists(self, request):
+        """Render the home page"""
+
+        if request.user.is_authenticated():
+            template='telemeta/lists.html'
+            playlists = get_playlists(request)
+            revisions = get_revisions(100)
+            searches = Search.objects.filter(username=request.user)
+            user_revisions = get_revisions(25, request.user)
+            return render(request, template, {'playlists': playlists, 'searches': searches,
+                                              'revisions': revisions, 'user_revisions': user_revisions })
+        else:
+            template = 'telemeta/messages.html'
+            mess = ugettext('Access not allowed')
+            title = ugettext('Lists') + ' : ' + mess
+            description = ugettext('Please login or contact the website administator to get a private access.')
+            messages.error(request, title)
+            return render(request, template, {'description' : description})
+
+    def edit_search(self, request, criteria=None):
+        year_min, year_max = MediaCollection.objects.all().recording_year_range()
+        rec_years = year_min and year_max and range(year_min, year_max + 1) or []
+        year_min, year_max = MediaCollection.objects.all().publishing_year_range()
+        pub_years = year_min and year_max and range(year_min, year_max + 1) or []
+        if request.user.is_authenticated():
+            searches = Search.objects.filter(username=request.user)
+        else:
+            searches = []
+        return render(request, 'telemeta/search_criteria.html', {
+            'rec_years': rec_years,
+            'pub_years': pub_years,
+            'ethnic_groups': MediaItem.objects.all().ethnic_groups(),
+            'criteria': criteria,
+            'searches': searches,
+        })
+
+    def handle_oai_request(self, request):
+        host = request.META['HTTP_HOST']
+        datasource  = TelemetaOAIDataSource()
+        repository_name = settings.TELEMETA_ORGANIZATION
+        url         = 'http://' + host + request.path
+        admin       = settings.ADMINS[0][1]
+        provider    = oai.DataProvider(datasource, repository_name, url, admin)
+        args        = request.GET.copy()
+        args.update(request.POST)
+        return HttpResponse(provider.handle(args), mimetype='text/xml')
+
+    def render_flatpage(self, request, path):
+        try:
+            content = pages.get_page_content(request, path)
+        except pages.MalformedPagePath:
+            return redirect(request.path + '/')
+
+        if isinstance(content, pages.PageAttachment):
+            return HttpResponse(content, content.mimetype())
+        else:
+            return render(request, 'telemeta/flatpage.html', {'page_content': content })
+
+    def logout(self, request):
+        auth.logout(request)
+        return redirect('telemeta-home')
+
+    def search(self, request, type = None):
+        """Perform a search through collections and items metadata"""
+        collections = MediaCollection.objects.enriched()
+        items = MediaItem.objects.enriched()
+        corpus = MediaCorpus.objects.all()
+        fonds  = MediaFonds.objects.all()
+        input = request.REQUEST
+        criteria = {}
+
+        switch = {
+            'pattern': lambda value: (
+                collections.quick_search(value),
+                items.quick_search(value),
+                corpus.quick_search(value),
+                fonds.quick_search(value),
+                ),
+            'title': lambda value: (
+                collections.word_search('title', value),
+                items.by_title(value),
+                corpus.word_search('title', value),
+                fonds.word_search('title', value)),
+            'location': lambda value: (
+                collections.by_location(Location.objects.get(name=value)),
+                items.by_location(Location.objects.get(name=value))),
+            'continent': lambda value: (
+                collections.by_continent(value),
+                items.filter(continent = value)),
+            'ethnic_group': lambda value: (
+                collections.by_ethnic_group(value),
+                items.filter(ethnic_group = value),
+                EthnicGroup.objects.get(pk=value)),
+            'creator': lambda value: (
+                collections.word_search('creator', value),
+                items.word_search('collection__creator', value)),
+            'collector': lambda value: (
+                collections.by_fuzzy_collector(value),
+                items.by_fuzzy_collector(value)),
+            'rec_year_from': lambda value: (
+                collections.by_recording_year(int(value), int(input.get('rec_year_to', value))),
+                items.by_recording_date(datetime.date(int(value), 1, 1),
+                                        datetime.date(int(input.get('rec_year_to', value)), 12, 31))),
+            'rec_year_to': lambda value: (collections, items),
+            'pub_year_from': lambda value: (
+                collections.by_publish_year(int(value), int(input.get('pub_year_to', value))),
+                items.by_publish_year(int(value), int(input.get('pub_year_to', value)))),
+            'pub_year_to': lambda value: (collections, items),
+            'sound': lambda value: (
+                collections.sound(),
+                items.sound()),
+            'instrument': lambda value: (
+                collections.by_instrument(value),
+                items.by_instrument(value)),
+        }
+
+        for key, value in input.items():
+            func = switch.get(key)
+            if func and value and value != "0":
+                try:
+                    res = func(value)
+                    if len(res)  > 4:
+                        collections, items, corpus, fonds, value = res
+                    elif len(res) == 4:
+                        collections, items, corpus, fonds = res
+                    elif len(res) == 3:
+                        collections, items, value = res
+                        corpus = corpus.none()
+                        fonds = fonds.none()
+                    else:
+                        collections, items = res
+                        corpus = corpus.none()
+                        fonds = fonds.none()
+
+                except ObjectDoesNotExist:
+                    collections = collections.none()
+                    items = items.none()
+                    corpus = corpus.none()
+                    fonds = fonds.none()
+
+                criteria[key] = value
+
+        # Save the search
+        user = request.user
+        if user:
+            if user.is_authenticated():
+                search = Search(username=user)
+                search.save()
+                if criteria:
+                    for key in criteria.keys():
+                        value = criteria[key]
+                        if key == 'ethnic_group':
+                            try:
+                                group = EthnicGroup.objects.get(value=value)
+                                value = group.id
+                            except:
+                                value = ''
+                        criter = Criteria(key=key, value=value)
+                        criter.save()
+                        search.criteria.add(criter)
+                    search.save()
+
+        if type is None:
+            if collections.count():
+                type = 'collections'
+            else:
+                type = 'items'
+
+        if type == 'items':
+            objects = items
+        elif type == 'collections':
+            objects = collections
+        elif type == 'corpus':
+            objects = corpus
+        elif type == 'fonds':
+            objects = fonds
+
+        return list_detail.object_list(request, objects,
+            template_name='telemeta/search_results.html', paginate_by=20,
+            extra_context={'criteria': criteria, 'collections_num': collections.count(),
+                'items_num': items.count(), 'corpus_num': corpus.count(), 'fonds_num': fonds.count(),
+                'type' : type,})
+
+    def complete_location(self, request, with_items=True):
+        input = request.REQUEST
+
+        token = input['q']
+        limit = int(input['limit'])
+        if with_items:
+            locations = MediaItem.objects.all().locations()
+        else:
+            locations = Location.objects.all()
+
+        locations = locations.filter(name__istartswith=token).order_by('name')[:limit]
+        data = [unicode(l) + " (%d items)" % l.items().count() for l in locations]
+
+        return HttpResponse("\n".join(data))
+
+    @method_decorator(login_required)
+    def users(self, request):
+        users = User.objects.all().order_by('last_name')
+        return render(request, 'telemeta/users.html', {'users': users})
diff --git a/telemeta/views/instrument.py b/telemeta/views/instrument.py
new file mode 100644 (file)
index 0000000..934c4de
--- /dev/null
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2007-2010 Samalyse SARL
+# Copyright (C) 2010-2012 Parisson SARL
+
+# This software is a computer program whose purpose is to backup, analyse,
+# transcode and stream any audio content with its metadata over a web frontend.
+
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+
+# Authors: Olivier Guilyardi <olivier@samalyse.com>
+#          Guillaume Pellerin <yomguy@parisson.com>
+
+
+from telemeta.views.core import *
+
+class InstrumentView(object):
+    """Provide Instrument web UI methods"""
+
+    @method_decorator(permission_required('telemeta.change_instrument'))
+    def edit_instrument(self, request):
+
+        instruments = Instrument.objects.all().order_by('name')
+        if instruments == None:
+            raise Http404
+        return render(request, 'telemeta/instrument_edit.html', {'instruments': instruments})
+
+    @method_decorator(permission_required('telemeta.add_instrument'))
+    def add_to_instrument(self, request):
+
+        if request.method == 'POST':
+            instrument = Instrument(name=request.POST['value'])
+            instrument.save()
+
+        return self.edit_instrument(request)
+
+    @method_decorator(permission_required('telemeta.change_instrument'))
+    def update_instrument(self, request):
+
+        if request.method == 'POST':
+            Instrument.objects.filter(id__in=request.POST.getlist('sel')).delete()
+
+        return self.edit_instrument(request)
+
+    @method_decorator(permission_required('telemeta.change_instrument'))
+    def edit_instrument_value(self, request, value_id):
+        instrument = Instrument.objects.get(id__exact=value_id)
+
+        return render(request, 'telemeta/instrument_edit_value.html', {'instrument': instrument})
+
+    @method_decorator(permission_required('telemeta.change_instrument'))
+    def update_instrument_value(self, request, value_id):
+
+        if request.method == 'POST':
+            instrument = Instrument.objects.get(id__exact=value_id)
+            instrument.name = request.POST["value"]
+            instrument.save()
+
+        return self.edit_instrument(request)
diff --git a/telemeta/views/item.py b/telemeta/views/item.py
new file mode 100644 (file)
index 0000000..5fea683
--- /dev/null
@@ -0,0 +1,589 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2007-2010 Samalyse SARL
+# Copyright (C) 2010-2012 Parisson SARL
+
+# This software is a computer program whose purpose is to backup, analyse,
+# transcode and stream any audio content with its metadata over a web frontend.
+
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+
+# Authors: Olivier Guilyardi <olivier@samalyse.com>
+#          Guillaume Pellerin <yomguy@parisson.com>
+
+
+from telemeta.views.core import *
+
+class ItemView(object):
+    """Provide Collections web UI methods"""
+
+    graphers = timeside.core.processors(timeside.api.IGrapher)
+    decoders = timeside.core.processors(timeside.api.IDecoder)
+    encoders = timeside.core.processors(timeside.api.IEncoder)
+    analyzers = timeside.core.processors(timeside.api.IAnalyzer)
+    cache_data = TelemetaCache(settings.TELEMETA_DATA_CACHE_DIR)
+    cache_export = TelemetaCache(settings.TELEMETA_EXPORT_CACHE_DIR)
+
+    def item_previous_next(self, item):
+        # Get previous and next items
+        pks = []
+        items = MediaItem.objects.filter(collection=item.collection)
+        items = items.order_by('code', 'old_code')
+
+        if len(items) > 1:
+            for it in items:
+                pks.append(it.pk)
+            for pk in pks:
+                if pk == item.pk:
+                    if pk == pks[0]:
+                        previous_pk = pks[-1]
+                        next_pk = pks[1]
+                    elif pk == pks[-1]:
+                        previous_pk = pks[-2]
+                        next_pk = pks[0]
+                    else:
+                        previous_pk = pks[pks.index(pk)-1]
+                        next_pk = pks[pks.index(pk)+1]
+                    for it in items:
+                        if it.pk == previous_pk:
+                            previous = it
+                        if it.pk == next_pk:
+                            next = it
+                    previous = previous.public_id
+                    next = next.public_id
+        else:
+             previous = item.public_id
+             next = item.public_id
+
+        return previous, next
+
+    def item_detail(self, request, public_id=None, marker_id=None, width=None, height=None,
+                        template='telemeta/mediaitem_detail.html'):
+        """Show the details of a given item"""
+
+        if not public_id and marker_id:
+            marker = MediaItemMarker.objects.get(public_id=marker_id)
+            item_id = marker.item_id
+            item = MediaItem.objects.get(id=item_id)
+        else:
+            item = MediaItem.objects.get(public_id=public_id)
+
+        item_public_access = item.public_access != 'none' or item.collection.public_access != 'none'
+        if not item_public_access and not (request.user.is_staff or request.user.is_superuser):
+            mess = ugettext('Access not allowed')
+            title = ugettext('Item') + ' : ' + public_id + ' : ' + mess
+            description = ugettext('Please login or contact the website administator to get a private access.')
+            messages.error(request, title)
+            return render(request, 'telemeta/messages.html', {'description' : description})
+
+        # Get TimeSide processors
+        formats = []
+        for encoder in self.encoders:
+            if settings.TELEMETA_DOWNLOAD_FORMATS:
+                if encoder.file_extension() in settings.TELEMETA_DOWNLOAD_FORMATS:
+                    formats.append({'name': encoder.format(), 'extension': encoder.file_extension()})
+            else:
+                formats.append({'name': encoder.format(), 'extension': encoder.file_extension()})
+
+        graphers = []
+        for grapher in self.graphers:
+            graphers.append({'name':grapher.name(), 'id': grapher.id()})
+        if request.REQUEST.has_key('grapher_id'):
+            grapher_id = request.REQUEST['grapher_id']
+        else:
+            try:
+                grapher_id = settings.TELEMETA_DEFAULT_GRAPHER_ID
+            except:
+                grapher_id = 'waveform'
+
+        previous, next = self.item_previous_next(item)
+        mime_type = self.item_analyze(item)
+        #FIXME: use mimetypes.guess_type
+        if 'quicktime' in mime_type:
+            mime_type = 'video/mp4'
+
+        playlists = get_playlists(request)
+        public_access = get_public_access(item.public_access, str(item.recorded_from_date).split('-')[0],
+                                                str(item.recorded_to_date).split('-')[0])
+
+        related_media = MediaItemRelated.objects.filter(item=item)
+        check_related_media(related_media)
+        revisions = Revision.objects.filter(element_type='item', element_id=item.id).order_by('-time')
+        if revisions:
+            last_revision = revisions[0]
+        else:
+            last_revision = None
+
+        format = ''
+        if Format.objects.filter(item=item):
+            format = item.format.get()
+
+        return render(request, template,
+                    {'item': item, 'export_formats': formats,
+                    'visualizers': graphers, 'visualizer_id': grapher_id,
+                    'audio_export_enabled': getattr(settings, 'TELEMETA_DOWNLOAD_ENABLED', True),
+                    'previous' : previous, 'next' : next, 'marker': marker_id, 'playlists' : playlists,
+                    'public_access': public_access, 'width': width, 'height': height,
+                    'related_media': related_media, 'mime_type': mime_type, 'last_revision': last_revision,
+                    'format': format,
+                    })
+
+    @method_decorator(permission_required('telemeta.change_mediaitem'))
+    def item_edit(self, request, public_id, template='telemeta/mediaitem_edit.html'):
+        """Edit a given item"""
+        item = MediaItem.objects.get(public_id=public_id)
+
+        formats = []
+        for encoder in self.encoders:
+            #FIXME: timeside cannot encode to FLAC and OGG now :'(
+            if encoder.file_extension() != 'ogg' and encoder.file_extension() != 'flac':
+                formats.append({'name': encoder.format(), 'extension': encoder.file_extension()})
+
+        graphers = []
+        for grapher in self.graphers:
+            graphers.append({'name':grapher.name(), 'id': grapher.id()})
+        if request.REQUEST.has_key('grapher_id'):
+            grapher_id = request.REQUEST['grapher_id']
+        else:
+            try:
+                grapher_id = settings.TELEMETA_DEFAULT_GRAPHER_ID
+            except:
+                grapher_id = 'waveform'
+
+        previous, next = self.item_previous_next(item)
+        mime_type = self.item_analyze(item)
+        #FIXME: use mimetypes.guess_type
+        if 'quicktime' in mime_type:
+            mime_type = 'video/mp4'
+
+        format, created = Format.objects.get_or_create(item=item)
+
+        if request.method == 'POST':
+            item_form = MediaItemForm(data=request.POST, files=request.FILES, instance=item, prefix='item')
+            format_form = FormatForm(data=request.POST, instance=format, prefix='format')
+            if item_form.is_valid() and format_form.is_valid():
+                item_form.save()
+                format_form.save()
+                code = item_form.cleaned_data['code']
+                if not code:
+                    code = str(item.id)
+                if item_form.files:
+                    self.cache_data.delete_item_data(code)
+                    self.cache_export.delete_item_data(code)
+                    flags = MediaItemTranscodingFlag.objects.filter(item=item)
+                    analyses = MediaItemAnalysis.objects.filter(item=item)
+                    for flag in flags:
+                        flag.delete()
+                    for analysis in analyses:
+                        analysis.delete()
+                item.set_revision(request.user)
+                return HttpResponseRedirect('/archives/items/'+code)
+        else:
+            item_form = MediaItemForm(instance=item, prefix='item')
+            format_form = FormatForm(instance=format, prefix='format')
+
+        forms = [item_form, format_form]
+        hidden_fields = ['item-copied_from_item', 'format-item']
+
+        return render(request, template,
+                    {'item': item, 'export_formats': formats,
+                    'visualizers': graphers, 'visualizer_id': grapher_id,
+                    'audio_export_enabled': getattr(settings, 'TELEMETA_DOWNLOAD_ENABLED', True),
+                    'forms': forms, 'previous' : previous, 'next' : next, 'mime_type': mime_type,
+                    'hidden_fields': hidden_fields,
+                    })
+
+    def related_media_item_stream(self, request, item_public_id, media_id):
+        item = MediaItem.objects.get(public_id=item_public_id)
+        media = MediaItemRelated.objects.get(item=item, id=media_id)
+        response = HttpResponse(stream_from_file(media.file.path), mimetype=media.mime_type)
+#        response['Content-Disposition'] = 'attachment; '+'filename='+media.title+'.'+ext
+        return response
+
+    @method_decorator(permission_required('telemeta.change_mediaitem'))
+    def related_media_edit(self, request, public_id, template):
+        item = MediaItem.objects.get(public_id=public_id)
+        MediaItemRelatedFormSet = inlineformset_factory(MediaItem, MediaItemRelated, form=MediaItemRelatedForm)
+        if request.method == 'POST':
+            formset = MediaItemRelatedFormSet(data=request.POST, files=request.FILES, instance=item)
+            if formset.is_valid():
+                formset.save()
+                item.set_revision(request.user)
+                return HttpResponseRedirect('/archives/items/'+public_id)
+        else:
+            formset = MediaItemRelatedFormSet(instance=item)
+
+        return render(request, template, {'item': item, 'formset': formset,})
+
+    @method_decorator(permission_required('telemeta.add_mediaitem'))
+    def item_add(self, request, public_id=None, template='telemeta/mediaitem_add.html'):
+        """Add an item"""
+        if public_id:
+            collection = MediaCollection.objects.get(public_id=public_id)
+            items = MediaItem.objects.filter(collection=collection)
+            code = auto_code(items, collection.code)
+            item = MediaItem(collection=collection, code=code)
+            format, created = Format.objects.get_or_create(item=item)
+        else:
+            item = MediaItem()
+            format = Format()
+
+        if request.method == 'POST':
+            item_form = MediaItemForm(data=request.POST, files=request.FILES, instance=item, prefix='item')
+            format_form = FormatForm(data=request.POST, instance=format, prefix='format')
+            if item_form.is_valid() and format_form.is_valid():
+                item_form.save()
+                item.set_revision(request.user)
+                format.item = item
+                format_form.save()
+                code = item_form.cleaned_data['code']
+                if not code:
+                    code = str(item.id)
+                return HttpResponseRedirect('/archives/items/'+code)
+        else:
+            item_form = MediaItemForm(instance=item, prefix='item')
+            format_form = FormatForm(instance=format, prefix='format')
+
+        forms = [item_form, format_form]
+        hidden_fields = ['item-copied_from_item', 'format-item']
+
+        return render(request, template, {'item': item, 'forms': forms, 'hidden_fields': hidden_fields,})
+
+    @method_decorator(permission_required('telemeta.add_mediaitem'))
+    def item_copy(self, request, public_id, template='telemeta/mediaitem_copy.html'):
+        """Copy a given item"""
+        if request.method == 'POST':
+            source_item = MediaItem.objects.get(public_id=public_id)
+            item = MediaItem()
+            format = Format()
+            item_form = MediaItemForm(data=request.POST, files=request.FILES, instance=item, prefix='item')
+            format_form = FormatForm(data=request.POST, instance=format, prefix='format')
+
+            if item_form.is_valid():
+                item_form.save()
+                code = item_form.cleaned_data['code']
+                if not code:
+                    code = str(item.id)
+                if format_form.is_valid():
+                    format.item = item
+                    format_form.save()
+
+                performances = MediaItemPerformance.objects.filter(media_item=source_item)
+                for performance in performances:
+                    performance.pk = None
+                    performance.id = None
+                    performance.media_item = item
+                    performance.save()
+
+                keywords = MediaItemKeyword.objects.filter(item=source_item)
+                for keyword in keywords:
+                    keyword.pk = None
+                    keyword.id = None
+                    keyword.item = item
+                    keyword.save()
+
+                item.set_revision(request.user)
+                return HttpResponseRedirect('/archives/items/'+code)
+        else:
+            item = MediaItem.objects.get(public_id=public_id)
+            items = MediaItem.objects.filter(collection=item.collection)
+            item.code = auto_code(items, item.collection.code)
+            item.approx_duration = ''
+            item_form = MediaItemForm(instance=item, prefix='item')
+            format, created = Format.objects.get_or_create(item=item)
+            format_form = FormatForm(instance=format, prefix='format')
+            item_form.code = item.code
+            item_form.file = None
+
+        forms = [item_form, format_form]
+        hidden_fields = ['item-copied_from_item', 'format-item']
+
+        return render(request, template, {'item': item, "forms": forms, 'hidden_fields': hidden_fields,})
+
+    @method_decorator(permission_required('telemeta.delete_mediaitem'))
+    def item_delete(self, request, public_id):
+        """Delete a given item"""
+        item = MediaItem.objects.get(public_id=public_id)
+        collection = item.collection
+        item.delete()
+        return HttpResponseRedirect('/archives/collections/'+collection.code)
+
+    def item_analyze(self, item):
+        analyses = MediaItemAnalysis.objects.filter(item=item)
+        mime_type = ''
+
+        if analyses:
+            for analysis in analyses:
+                if not item.approx_duration and analysis.analyzer_id == 'duration':
+                    value = analysis.value
+                    time = value.split(':')
+                    time[2] = time[2].split('.')[0]
+                    time = ':'.join(time)
+                    item.approx_duration = time
+                    item.save()
+                if analysis.analyzer_id == 'mime_type':
+                    mime_type = analysis.value
+        else:
+            analyzers = []
+            analyzers_sub = []
+            graphers_sub = []
+
+            if item.file:
+                decoder  = timeside.decoder.FileDecoder(item.file.path)
+                pipe = decoder
+
+                for analyzer in self.analyzers:
+                    subpipe = analyzer()
+                    analyzers_sub.append(subpipe)
+                    pipe = pipe | subpipe
+
+                try:
+                    sizes = settings.TELEMETA_DEFAULT_GRAPHER_SIZES
+                except:
+                    sizes = ['360x130', ]
+
+                for grapher in self.graphers:
+                    for size in sizes:
+                        width = size.split('x')[0]
+                        height = size.split('x')[1]
+                        image_file = '.'.join([item.public_id, grapher.id(), size.replace('x', '_'), 'png'])
+                        path = self.cache_data.dir + os.sep + image_file
+                        graph = grapher(width = int(width), height = int(height))
+                        graphers_sub.append({'graph' : graph, 'path': path})
+                        pipe = pipe | graph
+
+                pipe.run()
+
+                for grapher in graphers_sub:
+                    grapher['graph'].watermark('timeside', opacity=.6, margin=(5,5))
+                    f = open(grapher['path'], 'w')
+                    grapher['graph'].render(grapher['path'])
+                    f.close()
+
+                mime_type = decoder.format()
+                analysis = MediaItemAnalysis(item=item, name='MIME type',
+                                             analyzer_id='mime_type', unit='', value=mime_type)
+                analysis.save()
+                analysis = MediaItemAnalysis(item=item, name='Channels',
+                                             analyzer_id='channels',
+                                             unit='', value=decoder.channels())
+                analysis.save()
+                analysis = MediaItemAnalysis(item=item, name='Samplerate',
+                                             analyzer_id='samplerate', unit='Hz',
+                                             value=unicode(decoder.audiorate))
+                analysis.save()
+                analysis = MediaItemAnalysis(item=item, name='Resolution',
+                                             analyzer_id='resolution', unit='bits',
+                                             value=unicode(decoder.audiowidth))
+                analysis.save()
+                analysis = MediaItemAnalysis(item=item, name='Duration',
+                                             analyzer_id='duration', unit='s',
+                                             value=unicode(datetime.timedelta(0,decoder.duration)))
+                analysis.save()
+
+                for analyzer in analyzers_sub:
+                    value = analyzer.result()
+                    analysis = MediaItemAnalysis(item=item, name=analyzer.name(),
+                                                 analyzer_id=analyzer.id(),
+                                                 unit=analyzer.unit(), value=str(value))
+                    analysis.save()
+
+#                FIXME: parse tags on first load
+#                tags = decoder.tags
+
+        return mime_type
+
+    def item_analyze_xml(self, request, public_id):
+        item = MediaItem.objects.get(public_id=public_id)
+        analyses = MediaItemAnalysis.objects.filter(item=item)
+        analyzers = []
+        for analysis in analyses:
+            analyzers.append(analysis.to_dict())
+        mime_type = 'text/xml'
+        response = HttpResponse(self.cache_data.get_analyzer_xml(analyzers), mimetype=mime_type)
+        response['Content-Disposition'] = 'attachment; filename='+public_id+'.xml'
+        return response
+
+    def item_visualize(self, request, public_id, visualizer_id, width, height):
+        item = MediaItem.objects.get(public_id=public_id)
+        mime_type = 'image/png'
+        grapher_id = visualizer_id
+
+        for grapher in self.graphers:
+            if grapher.id() == grapher_id:
+                break
+
+        if grapher.id() != grapher_id:
+            raise Http404
+
+        size = width + '_' + height
+        image_file = '.'.join([public_id, grapher_id, size, 'png'])
+
+        if not self.cache_data.exists(image_file):
+            if item.file:
+                path = self.cache_data.dir + os.sep + image_file
+                decoder  = timeside.decoder.FileDecoder(item.file.path)
+                graph = grapher(width = int(width), height = int(height))
+                pipe = decoder | graph
+                pipe.run()
+                graph.watermark('timeside', opacity=.6, margin=(5,5))
+                f = open(path, 'w')
+                graph.render(path)
+                f.close()
+
+        response = HttpResponse(self.cache_data.read_stream_bin(image_file), mimetype=mime_type)
+        return response
+
+    def list_export_extensions(self):
+        "Return the recognized item export file extensions, as a list"
+        list = []
+        for encoder in self.encoders:
+            list.append(encoder.file_extension())
+        #FIXME: MP4
+        list.append('mp4')
+        return list
+
+    def item_export(self, request, public_id, extension):
+        """Export a given media item in the specified format (OGG, FLAC, ...)"""
+
+        item = MediaItem.objects.get(public_id=public_id)
+        public_access = get_public_access(item.public_access,
+                                          str(item.recorded_from_date).split('-')[0],
+                                          str(item.recorded_to_date).split('-')[0])
+
+        if (not public_access or not extension in settings.TELEMETA_STREAMING_FORMATS) and \
+                    not (request.user.has_perm('telemeta.can_play_all_items') or request.user.is_superuser):
+            mess = ugettext('Access not allowed')
+            title = 'Item file : ' + public_id + '.' + extension + ' : ' + mess
+            description = ugettext('Please login or contact the website administator to get a private access.')
+            messages.error(request, title)
+            return render(request, 'telemeta/messages.html', {'description' : description})
+
+        #FIXME: MP4 handling in TimeSide
+        if 'mp4' in extension:
+            mime_type = 'video/mp4'
+            video = item.file.path
+            response = HttpResponse(stream_from_file(video), mimetype = mime_type)
+            response['Content-Disposition'] = 'attachment'
+            return response
+
+        if 'webm' in extension:
+            mime_type = 'video/webm'
+            video = item.file.path
+            response = HttpResponse(stream_from_file(video), mimetype = mime_type)
+            response['Content-Disposition'] = 'attachment'
+            return response
+
+        for encoder in self.encoders:
+            if encoder.file_extension() == extension:
+                break
+
+        if encoder.file_extension() != extension:
+            raise Http404('Unknown export file extension: %s' % extension)
+
+        mime_type = encoder.mime_type()
+        file = public_id + '.' + encoder.file_extension()
+        audio = item.file.path
+
+        flag = MediaItemTranscodingFlag.objects.filter(item=item, mime_type=mime_type)
+        if not flag:
+            flag = MediaItemTranscodingFlag(item=item, mime_type=mime_type)
+            flag.value = False
+            flag.save()
+        else:
+            flag = flag[0]
+
+        format = self.item_analyze(item)
+        dc_metadata = dublincore.express_item(item).to_list()
+        mapping = DublinCoreToFormatMetadata(extension)
+        metadata = mapping.get_metadata(dc_metadata)
+
+        if mime_type in format:
+            # source > stream
+            if not extension in mapping.unavailable_extensions:
+                proc = encoder(audio)
+                proc.set_metadata(metadata)
+                try:
+                    proc.write_metadata()
+                except:
+                    pass
+            response = HttpResponse(stream_from_file(audio), mimetype = mime_type)
+#            fsock = open(audio, 'r')
+#            response = HttpResponse(fsock, mimetype = mime_type)
+        else:
+            media = self.cache_export.dir + os.sep + file
+            if not self.cache_export.exists(file) or not flag.value:
+                # source > encoder > stream
+                decoder = timeside.decoder.FileDecoder(audio)
+                decoder.setup()
+                proc = encoder(media, streaming=True)
+                proc.setup(channels=decoder.channels(), samplerate=decoder.samplerate())
+                if extension in mapping.unavailable_extensions:
+                    metadata=None
+                response = HttpResponse(stream_from_processor(decoder, proc, flag, metadata=metadata), mimetype = mime_type)
+            else:
+                # cache > stream
+                response = HttpResponse(self.cache_export.read_stream_bin(file), mimetype = mime_type)
+
+        response['Content-Disposition'] = 'attachment'
+        return response
+
+    def item_playlist(self, request, public_id, template, mimetype):
+        try:
+            item = MediaItem.objects.get(public_id=public_id)
+        except ObjectDoesNotExist:
+            raise Http404
+
+        template = loader.get_template(template)
+        context = RequestContext(request, {'item': item, 'host': request.META['HTTP_HOST']})
+        return HttpResponse(template.render(context), mimetype=mimetype)
+
+    @method_decorator(permission_required('telemeta.change_mediaitem'))
+    def item_performances_edit(self, request, public_id, template):
+        item = MediaItem.objects.get(public_id=public_id)
+        PerformanceFormSet = inlineformset_factory(MediaItem, MediaItemPerformance, form=MediaItemPerformanceForm)
+        if request.method == 'POST':
+            formset = PerformanceFormSet(data=request.POST, instance=item)
+            if formset.is_valid():
+                formset.save()
+                return HttpResponseRedirect('/archives/items/'+public_id)
+        else:
+            formset = PerformanceFormSet(instance=item)
+        return render(request, template, {'item': item, 'formset': formset,})
+
+    @method_decorator(permission_required('telemeta.change_mediaitem'))
+    def item_keywords_edit(self, request, public_id, template):
+        item = MediaItem.objects.get(public_id=public_id)
+        FormSet = inlineformset_factory(MediaItem, MediaItemKeyword)
+        if request.method == 'POST':
+            formset = FormSet(data=request.POST, instance=item)
+            if formset.is_valid():
+                formset.save()
+                return HttpResponseRedirect('/archives/items/'+public_id)
+        else:
+            formset = FormSet(instance=item)
+        return render(request, template, {'item': item, 'formset': formset,})
+
diff --git a/telemeta/views/marker.py b/telemeta/views/marker.py
new file mode 100644 (file)
index 0000000..325275d
--- /dev/null
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2010-2012 Parisson SARL
+
+# This software is a computer program whose purpose is to backup, analyse,
+# transcode and stream any audio content with its metadata over a web frontend.
+
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+
+# Authors: Guillaume Pellerin <yomguy@parisson.com>
+
+
+from telemeta.views.core import *
+
+
+class MarkerView(object):
+    """Provide Collections web UI methods"""
+
+    @jsonrpc_method('telemeta.add_marker')
+    def add_marker(request, marker):
+        # marker must be a dict
+        if isinstance(marker, dict):
+            item_id = marker['item_id']
+            item = MediaItem.objects.get(id=item_id)
+            m = MediaItemMarker(item=item)
+            m.public_id = marker['public_id']
+            m.time = float(marker['time'])
+            m.title = marker['title']
+            m.description = marker['description']
+            m.author = User.objects.get(username=marker['author'])
+            m.save()
+            m.set_revision(request.user)
+        else:
+            raise 'Error : Bad marker dictionnary'
+
+    @jsonrpc_method('telemeta.del_marker')
+    def del_marker(request, public_id):
+        m = MediaItemMarker.objects.get(public_id=public_id)
+        m.delete()
+
+    @jsonrpc_method('telemeta.get_markers')
+    def get_markers(request, item_id):
+        item = MediaItem.objects.get(id=item_id)
+        markers = MediaItemMarker.objects.filter(item=item)
+        list = []
+        for marker in markers:
+            dict = {}
+            dict['public_id'] = marker.public_id
+            dict['time'] = str(marker.time)
+            dict['title'] = marker.title
+            dict['description'] = marker.description
+            dict['author'] = marker.author.username
+            list.append(dict)
+        return list
+
+    @jsonrpc_method('telemeta.update_marker')
+    def update_marker(request, marker):
+        if isinstance(marker, dict):
+            m = MediaItemMarker.objects.get(public_id=marker['public_id'])
+            m.time = float(marker['time'])
+            m.title = marker['title']
+            m.description = marker['description']
+            m.save()
+            m.set_revision(request.user)
+        else:
+            raise 'Error : Bad marker dictionnary'
+
+    @jsonrpc_method('telemeta.get_marker_id')
+    def get_marker_id(request, public_id):
+        marker = MediaItemMarker.objects.get(public_id=public_id)
+        return marker.id
diff --git a/telemeta/views/playlist.py b/telemeta/views/playlist.py
new file mode 100644 (file)
index 0000000..5ad9fec
--- /dev/null
@@ -0,0 +1,131 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2010-2012 Parisson SARL
+
+# This software is a computer program whose purpose is to backup, analyse,
+# transcode and stream any audio content with its metadata over a web frontend.
+
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+
+# Authors: Guillaume Pellerin <yomguy@parisson.com>
+
+
+from telemeta.views.core import *
+
+
+class PlaylistView(object):
+    """Provide Playlist web UI methods"""
+
+    @jsonrpc_method('telemeta.add_playlist')
+    def add_playlist(request, playlist):
+        # playlist must be a dict
+        if isinstance(playlist, dict):
+            m = Playlist()
+            m.public_id = playlist['public_id']
+            m.title = playlist['title']
+            m.description = playlist['description']
+            m.author = request.user
+            m.save()
+        else:
+            raise 'Error : Bad playlist dictionnary'
+
+    @jsonrpc_method('telemeta.del_playlist')
+    def del_playlist(request, public_id):
+        m = Playlist.objects.get(public_id=public_id)
+        m.delete()
+
+    @jsonrpc_method('telemeta.update_playlist')
+    def update_playlist(request, playlist):
+        if isinstance(playlist, dict):
+            m = Playlist.objects.get(public_id=playlist['public_id'])
+            m.title = playlist['title']
+            m.description = playlist['description']
+            m.save()
+        else:
+            raise 'Error : Bad playlist dictionnary'
+
+    @jsonrpc_method('telemeta.add_playlist_resource')
+    def add_playlist_resource(request, playlist_id, playlist_resource):
+        # playlist_resource must be a dict
+        if isinstance(playlist_resource, dict):
+            m = PlaylistResource()
+            m.public_id = playlist_resource['public_id']
+            m.playlist = Playlist.objects.get(public_id=playlist_id, author=request.user)
+            m.resource_type = playlist_resource['resource_type']
+            m.resource_id = playlist_resource['resource_id']
+            m.save()
+        else:
+            raise 'Error : Bad playlist_resource dictionnary'
+
+    @jsonrpc_method('telemeta.del_playlist_resource')
+    def del_playlist_resource(request, public_id):
+        m = PlaylistResource.objects.get(public_id=public_id)
+        m.delete()
+
+
+    def playlist_csv_export(self, request, public_id, resource_type):
+        playlist = Playlist.objects.get(public_id=public_id, author=request.user)
+        resources = PlaylistResource.objects.filter(playlist=playlist)
+        response = HttpResponse(mimetype='text/csv')
+        response['Content-Disposition'] = 'attachment; filename='+playlist.title+'_'+resource_type+'.csv'
+        writer = UnicodeWriter(response)
+
+        elements = []
+        for resource in resources:
+            if resource_type == 'items':
+                if resource.resource_type == 'collection':
+                    collection = MediaCollection.objects.get(id=resource.resource_id)
+                    collection_items = MediaItem.objects.filter(collection=collection)
+                    for item in collection_items:
+                        elements.append(item)
+                elif resource.resource_type == 'item':
+                    item = MediaItem.objects.get(id=resource.resource_id)
+                    elements.append(item)
+
+            elif resource_type == 'collections':
+                if resource.resource_type == 'collection':
+                    collection = MediaCollection.objects.get(id=resource.resource_id)
+                    elements.append(collection)
+
+        if elements:
+            element = elements[0].to_dict()
+            tags = element.keys()
+            # code and title on the two first column
+            tags.remove('code')
+            tags.remove('title')
+            tags.sort()
+            tags.insert(0, 'title')
+            tags.insert(0, 'code')
+            writer.writerow(tags)
+
+            for element in elements:
+                data = []
+                element = element.to_dict()
+                for tag in tags:
+                    data.append(element[tag])
+                writer.writerow(data)
+        return response
+
diff --git a/telemeta/views/profile.py b/telemeta/views/profile.py
new file mode 100644 (file)
index 0000000..322ece8
--- /dev/null
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2007-2010 Samalyse SARL
+# Copyright (C) 2010-2012 Parisson SARL
+
+# This software is a computer program whose purpose is to backup, analyse,
+# transcode and stream any audio content with its metadata over a web frontend.
+
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+
+# Authors: Olivier Guilyardi <olivier@samalyse.com>
+#          Guillaume Pellerin <yomguy@parisson.com>
+
+
+from telemeta.views.core import *
+
+
+class ProfileView(object):
+    """Provide Collections web UI methods"""
+
+    @method_decorator(login_required)
+    def profile_detail(self, request, username, template='telemeta/profile_detail.html'):
+        user = User.objects.get(username=username)
+        try:
+            profile = user.get_profile()
+        except:
+            profile = None
+        playlists = get_playlists(request, user)
+        user_revisions = get_revisions(25, user)
+
+        return render(request, template, {'profile' : profile, 'usr': user, 'playlists': playlists,
+                                          'user_revisions': user_revisions})
+
+    @method_decorator(login_required)
+    def profile_edit(self, request, username, template='telemeta/profile_edit.html'):
+        if request.user.is_superuser:
+            user_hidden_fields = ['profile-user', 'user-password', 'user-last_login', 'user-date_joined']
+        else:
+            user_hidden_fields = ['user-username', 'user-is_staff', 'profile-user', 'user-is_active',
+                         'user-password', 'user-last_login', 'user-date_joined', 'user-groups',
+                         'user-user_permissions', 'user-is_superuser', 'profile-expiration_date']
+
+        user = User.objects.get(username=username)
+        if user != request.user and not request.user.is_staff:
+            mess = ugettext('Access not allowed')
+            title = ugettext('User profile') + ' : ' + username + ' : ' + mess
+            description = ugettext('Please login or contact the website administator to get a private access.')
+            messages.error(request, title)
+            return render(request, 'telemeta/messages.html', {'description' : description})
+
+        try:
+            profile = user.get_profile()
+        except:
+            profile = UserProfile(user=user)
+
+        if request.method == 'POST':
+            user_form = UserChangeForm(request.POST, instance=user, prefix='user')
+            profile_form = UserProfileForm(request.POST, instance=profile, prefix='profile')
+            if user_form.is_valid() and profile_form.is_valid():
+                user_form.save()
+                profile_form.save()
+                return HttpResponseRedirect('/accounts/'+username+'/profile/')
+        else:
+            user_form = UserChangeForm(instance=user, prefix='user')
+            profile_form = UserProfileForm(instance=profile, prefix='profile')
+            forms = [user_form, profile_form]
+        return render(request, template, {'forms': forms, 'usr': user,
+                                'user_hidden_fields': user_hidden_fields})
+
diff --git a/telemeta/views/ressource.py b/telemeta/views/ressource.py
new file mode 100644 (file)
index 0000000..9be8764
--- /dev/null
@@ -0,0 +1,187 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2007-2010 Samalyse SARL
+# Copyright (C) 2010-2012 Parisson SARL
+
+# This software is a computer program whose purpose is to backup, analyse,
+# transcode and stream any audio content with its metadata over a web frontend.
+
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+
+# Authors: Olivier Guilyardi <olivier@samalyse.com>
+#          Guillaume Pellerin <yomguy@parisson.com>
+
+
+from telemeta.views.core import *
+
+
+class ResourceView(object):
+    """Provide Resource web UI methods"""
+
+    types = {'corpus':
+                {'model': MediaCorpus,
+                'form' : MediaCorpusForm,
+                'related': MediaCorpusRelated,
+                'related_form': MediaCorpusRelatedForm,
+                'parent': MediaFonds,
+                },
+            'fonds':
+                {'model': MediaFonds,
+                'form' : MediaFondsForm,
+                'related': MediaFondsRelated,
+                'related_form': MediaFondsRelatedForm,
+                'parent': None,
+                }
+            }
+
+    def setup(self, type):
+        self.model = self.types[type]['model']
+        self.form = self.types[type]['form']
+        self.related = self.types[type]['related']
+        self.related_form = self.types[type]['related_form']
+        self.parent = self.types[type]['parent']
+        self.type = type
+
+    def detail(self, request, type, public_id, template='telemeta/resource_detail.html'):
+        self.setup(type)
+        resource = self.model.objects.get(code=public_id)
+        children = resource.children.all()
+        children = children.order_by('code')
+        related_media = self.related.objects.filter(resource=resource)
+        check_related_media(related_media)
+        playlists = get_playlists(request)
+        revisions = Revision.objects.filter(element_type=type, element_id=resource.id).order_by('-time')
+        if revisions:
+            last_revision = revisions[0]
+        else:
+            last_revision = None
+        if self.parent:
+            parents = self.parent.objects.filter(children=resource)
+        else:
+            parents = []
+
+        return render(request, template, {'resource': resource, 'type': type, 'children': children,
+                        'related_media': related_media, 'parents': parents, 'playlists': playlists,
+                        'last_revision': last_revision })
+
+    @jsonrpc_method('telemeta.change_fonds')
+    @jsonrpc_method('telemeta.change_corpus')
+    def edit(self, request, type, public_id, template='telemeta/resource_edit.html'):
+        self.setup(type)
+        resource = self.model.objects.get(code=public_id)
+        if request.method == 'POST':
+            form = self.form(data=request.POST, files=request.FILES, instance=resource)
+            if form.is_valid():
+                code = form.cleaned_data['code']
+                if not code:
+                    code = public_id
+                form.save()
+                resource.set_revision(request.user)
+                return HttpResponseRedirect('/archives/'+self.type+'/'+code)
+        else:
+            form = self.form(instance=resource)
+        return render(request, template, {'resource': resource, 'type': type, 'form': form,})
+
+    @jsonrpc_method('telemeta.add_fonds')
+    @jsonrpc_method('telemeta.add_corpus')
+    def add(self, request, type, template='telemeta/resource_add.html'):
+        self.setup(type)
+        resource = self.model()
+        if request.method == 'POST':
+            form = self.form(data=request.POST, files=request.FILES, instance=resource)
+            if form.is_valid():
+                code = form.cleaned_data['code']
+                if not code:
+                    code = public_id
+                form.save()
+                resource.set_revision(request.user)
+                return HttpResponseRedirect('/archives/'+self.type +'/'+code)
+        else:
+            form = self.form(instance=resource)
+        return render(request, template, {'resource': resource, 'type': type, 'form': form,})
+
+    @jsonrpc_method('telemeta.add_fonds')
+    @jsonrpc_method('telemeta.add_corpus')
+    def copy(self, request, type, public_id, template='telemeta/resource_edit.html'):
+        self.setup(type)
+        if request.method == 'POST':
+            resource = self.model()
+            form = self.form(data=request.POST, files=request.FILES, instance=resource)
+            if form.is_valid():
+                code = form.cleaned_data['code']
+                if not code:
+                    code = public_id
+                resource.save()
+                resource.set_revision(request.user)
+                return HttpResponseRedirect('/archives/'+self.type +'/'+code)
+        else:
+            resource = self.model.objects.get(code=public_id)
+            form = self.form(instance=resource)
+        return render(request, template, {'resource': resource, 'type': type, "form": form,})
+
+    def playlist(self, request, type, public_id, template, mimetype):
+        self.setup(type)
+        try:
+            resource = self.model.objects.get(code=public_id)
+        except ObjectDoesNotExist:
+            raise Http404
+
+        template = loader.get_template(template)
+        context = RequestContext(request, {'resource': resource, 'host': request.META['HTTP_HOST']})
+        return HttpResponse(template.render(context), mimetype=mimetype)
+
+    @jsonrpc_method('telemeta.del_fonds')
+    @jsonrpc_method('telemeta.del_corpus')
+    def delete(self, request, type, public_id):
+        self.setup(type)
+        resource = self.model.objects.get(code=public_id)
+        resource.delete()
+        return HttpResponseRedirect('/archives/'+self.type+'/')
+
+    def related_stream(self, request, type, public_id, media_id):
+        self.setup(type)
+        resource = self.model.objects.get(code=public_id)
+        media = self.related.objects.get(resource=resource, id=media_id)
+        response = HttpResponse(stream_from_file(media.file.path), mimetype=media.mime_type)
+        return response
+
+    @jsonrpc_method('telemeta.add_fonds_related_media')
+    @jsonrpc_method('telemeta.add_corpus_related_media')
+    def related_edit(self, request, type, public_id, template):
+        self.setup(type)
+        resource = self.model.objects.get(code=public_id)
+        ResourceRelatedFormSet = inlineformset_factory(self.model, self.related, form=self.related_form)
+        if request.method == 'POST':
+            formset = ResourceRelatedFormSet(data=request.POST, files=request.FILES, instance=resource)
+            if formset.is_valid():
+                formset.save()
+                resource.set_revision(request.user)
+                return HttpResponseRedirect('/archives/'+self.type+'/'+public_id)
+        else:
+            formset = ResourceRelatedFormSet(instance=resource)
+        return render(request, template, {'resource': resource, 'type': type, 'formset': formset,})
+
+