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