From 861b89de4df089946a51d0b68359c860198c5ac2 Mon Sep 17 00:00:00 2001 From: yomguy Date: Mon, 17 Sep 2012 14:43:02 +0200 Subject: [PATCH] split web.base into views.* --- telemeta/urls.py | 4 +- telemeta/views/__init__.py | 46 + telemeta/views/admin.py | 152 +++ telemeta/views/base.py | 1733 ---------------------------------- telemeta/views/collection.py | 162 ++++ telemeta/views/core.py | 216 +++++ telemeta/views/feed.py | 93 ++ telemeta/views/geo.py | 77 ++ telemeta/views/home.py | 275 ++++++ telemeta/views/instrument.py | 82 ++ telemeta/views/item.py | 589 ++++++++++++ telemeta/views/marker.py | 94 ++ telemeta/views/playlist.py | 131 +++ telemeta/views/profile.py | 92 ++ telemeta/views/ressource.py | 187 ++++ 15 files changed, 2198 insertions(+), 1735 deletions(-) create mode 100644 telemeta/views/admin.py delete mode 100644 telemeta/views/base.py create mode 100644 telemeta/views/collection.py create mode 100644 telemeta/views/core.py create mode 100644 telemeta/views/feed.py create mode 100644 telemeta/views/geo.py create mode 100644 telemeta/views/home.py create mode 100644 telemeta/views/instrument.py create mode 100644 telemeta/views/item.py create mode 100644 telemeta/views/marker.py create mode 100644 telemeta/views/playlist.py create mode 100644 telemeta/views/profile.py create mode 100644 telemeta/views/ressource.py diff --git a/telemeta/urls.py b/telemeta/urls.py index e9c1a74c..8b762f8f 100644 --- a/telemeta/urls.py +++ b/telemeta/urls.py @@ -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() diff --git a/telemeta/views/__init__.py b/telemeta/views/__init__.py index 40a96afc..e4b9ff2b 100644 --- a/telemeta/views/__init__.py +++ b/telemeta/views/__init__.py @@ -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 + +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 index 00000000..d483c89a --- /dev/null +++ b/telemeta/views/admin.py @@ -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 +# Guillaume Pellerin + + +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 index 30ad004d..00000000 --- a/telemeta/views/base.py +++ /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 -# Guillaume Pellerin - -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 = 'modified by ' + revision.user.username + ' on ' + unicode(revision.time) + '

' - dict = element.to_dict() - for tag in dict.keys(): - try: - value = dict[tag] - if value != '': - description += tag + ' : ' + value + '
' - 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 index 00000000..de2677fa --- /dev/null +++ b/telemeta/views/collection.py @@ -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 +# Guillaume Pellerin + + +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 index 00000000..922a4f47 --- /dev/null +++ b/telemeta/views/core.py @@ -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 +# Guillaume Pellerin + +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 index 00000000..def5dc7a --- /dev/null +++ b/telemeta/views/feed.py @@ -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 + + +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 = 'modified by ' + revision.user.username + ' on ' + unicode(revision.time) + '

' + dict = element.to_dict() + for tag in dict.keys(): + try: + value = dict[tag] + if value != '': + description += tag + ' : ' + value + '
' + 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 index 00000000..fa4adc18 --- /dev/null +++ b/telemeta/views/geo.py @@ -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 +# Guillaume Pellerin + + +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 index 00000000..ec768ac5 --- /dev/null +++ b/telemeta/views/home.py @@ -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 +# Guillaume Pellerin + + +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 index 00000000..934c4de3 --- /dev/null +++ b/telemeta/views/instrument.py @@ -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 +# Guillaume Pellerin + + +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 index 00000000..5fea6834 --- /dev/null +++ b/telemeta/views/item.py @@ -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 +# Guillaume Pellerin + + +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 index 00000000..325275d0 --- /dev/null +++ b/telemeta/views/marker.py @@ -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 + + +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 index 00000000..5ad9fec1 --- /dev/null +++ b/telemeta/views/playlist.py @@ -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 + + +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 index 00000000..322ece81 --- /dev/null +++ b/telemeta/views/profile.py @@ -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 +# Guillaume Pellerin + + +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 index 00000000..9be87646 --- /dev/null +++ b/telemeta/views/ressource.py @@ -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 +# Guillaume Pellerin + + +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,}) + + -- 2.39.5