source: telemeta/web/base.py @ df08f22

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

fix exported item metadata writing

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