]> git.parisson.com Git - telemeta.git/commitdiff
Refactoring / splitting models
authorGuillaume Pellerin <yomguy@parisson.com>
Tue, 20 Jan 2015 16:07:30 +0000 (17:07 +0100)
committerGuillaume Pellerin <yomguy@parisson.com>
Tue, 20 Jan 2015 16:07:30 +0000 (17:07 +0100)
16 files changed:
telemeta/admin.py
telemeta/models/__init__.py
telemeta/models/collection.py [new file with mode: 0644]
telemeta/models/core.py
telemeta/models/corpus.py [new file with mode: 0644]
telemeta/models/dublincore.py
telemeta/models/fields.py [new file with mode: 0644]
telemeta/models/fonds.py [new file with mode: 0644]
telemeta/models/format.py
telemeta/models/identifier.py [new file with mode: 0644]
telemeta/models/instrument.py
telemeta/models/item.py [new file with mode: 0644]
telemeta/models/media.py [deleted file]
telemeta/models/playlist.py [new file with mode: 0644]
telemeta/models/resource.py [new file with mode: 0644]
telemeta/models/utils.py [new file with mode: 0644]

index 4bcb3805817e861c56c44fa8e050e6afa7062f10..8988f4e2197302ca38e179ccd8cfbd37bcc11a73 100644 (file)
@@ -1,9 +1,13 @@
 # -*- coding: utf-8 -*-
-from telemeta.models.media import *
+from telemeta.models.fonds import *
+from telemeta.models.corpus import *
+from telemeta.models.collection import *
+from telemeta.models.item import *
 from telemeta.models.instrument import *
 from telemeta.models.location import *
 from telemeta.models.language import *
 from telemeta.models.system import *
+from telemeta.models.format import *
 from django.contrib import admin
 from django.contrib.auth.models import User
 from django.contrib.auth.admin import UserAdmin
index e4e118c5723259d016421d9dc396a46639eab7fa..99f228e869ca47fa16a9009fbaf27bb08ef33e99 100644 (file)
@@ -34,7 +34,8 @@
 # Author: Olivier Guilyardi <olivier@samalyse.com>
 #         Guillaume Pellerin <yomguy@parisson.com>
 
-from media import *
+
+from identifier import *
 from location import *
 from instrument import *
 from enum import *
@@ -43,4 +44,10 @@ from query import *
 from dublincore import *
 from language import *
 from format import *
-
+from identifier import *
+from fonds import *
+from corpus import *
+from collection import *
+from item import *
+from resource import *
+from playlist import *
diff --git a/telemeta/models/collection.py b/telemeta/models/collection.py
new file mode 100644 (file)
index 0000000..e3d640c
--- /dev/null
@@ -0,0 +1,288 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2010 Samalyse SARL
+# Copyright (C) 2010-2014 Parisson SARL
+
+# This software is a computer program whose purpose is to backup, analyse,
+# transcode and stream any audio content with its metadata over a web frontend.
+
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+#
+# Authors: Olivier Guilyardi <olivier@samalyse.com>
+#          David LIPSZYC <davidlipszyc@gmail.com>
+#          Guillaume Pellerin <yomguy@parisson.com>
+
+
+from __future__ import division
+from django.utils.translation import ugettext_lazy as _
+from telemeta.models.core import *
+from telemeta.models.query import *
+from telemeta.models.identifier import *
+from telemeta.models.resource import *
+
+# Special code regex of collections for the branch
+collection_published_code_regex = getattr(settings, 'COLLECTION_PUBLISHED_CODE_REGEX', '[A-Za-z0-9._-]*')
+collection_unpublished_code_regex = getattr(settings, 'COLLECTION_UNPUBLISHED_CODE_REGEX', '[A-Za-z0-9._-]*')
+
+# CREM
+#collection_published_code_regex   = 'CNRSMH_E_[0-9]{4}(?:_[0-9]{3}){2}'
+#collection_unpublished_code_regex = 'CNRSMH_I_[0-9]{4}_[0-9]{3}'
+
+collection_code_regex = '(?:%s|%s)' % (collection_published_code_regex,
+                                       collection_unpublished_code_regex)
+
+
+class MediaCollection(MediaResource):
+    "Describe a collection of items"
+
+    element_type = 'collection'
+
+    def is_valid_collection_code(value):
+        "Check if the collection code is well formed"
+        regex = '^' + collection_code_regex + '$'
+        if not re.match(regex, value):
+            raise ValidationError(u'%s is not a valid collection code' % value)
+
+    # General informations
+    title                 = CharField(_('title'), required=True)
+    alt_title             = CharField(_('original title / translation'))
+    creator               = CharField(_('depositor / contributor'), help_text=_('First name, Last name ; First name, Last name'))
+    description           = TextField(_('description'))
+    recording_context     = WeakForeignKey('RecordingContext', related_name="collections", verbose_name=_('recording context'))
+    recorded_from_year    = IntegerField(_('recording year (from)'), help_text=_('YYYY'))
+    recorded_to_year      = IntegerField(_('recording year (until)'), help_text=_('YYYY'))
+    year_published        = IntegerField(_('year published'), help_text=_('YYYY'))
+    public_access         = CharField(_('access type'), choices=PUBLIC_ACCESS_CHOICES, max_length=16, default="metadata")
+
+    # Geographic and cultural informations
+    # See "countries" and "ethnic_groups" methods below
+
+    # Legal notices
+    collector             = CharField(_('recordist'), help_text=_('First name, Last name ; First name, Last name'))
+    publisher             = WeakForeignKey('Publisher', related_name="collections", verbose_name=_('publisher'))
+    publisher_collection  = WeakForeignKey('PublisherCollection', related_name="collections", verbose_name=_('publisher collection'))
+    publisher_serial      = CharField(_('publisher serial number'))
+    booklet_author        = CharField(_('booklet author'), blank=True)
+    reference             = CharField(_('publisher reference'))
+    external_references   = TextField(_('bibliographic references'))
+
+    auto_period_access    = BooleanField(_('automatic access after a rolling period'), default=True)
+    legal_rights          = WeakForeignKey('LegalRight', related_name="collections", verbose_name=_('legal rights'))
+
+    # Archiving data
+    code                  = CharField(_('code'), unique=True, required=True, validators=[is_valid_collection_code])
+    old_code              = CharField(_('old code'), unique=False, null=True, blank=True)
+    acquisition_mode      = WeakForeignKey('AcquisitionMode', related_name="collections", verbose_name=_('mode of acquisition'))
+    cnrs_contributor      = CharField(_('CNRS depositor'))
+    copy_type             = WeakForeignKey('CopyType', related_name="collections", verbose_name=_('copy type'))
+    metadata_author       = WeakForeignKey('MetadataAuthor', related_name="collections", verbose_name=_('record author'))
+    booklet_description   = TextField(_('related documentation'))
+    publishing_status     = WeakForeignKey('PublishingStatus', related_name="collections", verbose_name=_('secondary edition'))
+    status                = WeakForeignKey('Status', related_name="collections", verbose_name=_('collection status'))
+    alt_copies            = TextField(_('copies'))
+    comment               = TextField(_('comment'))
+    metadata_writer       = WeakForeignKey('MetadataWriter', related_name="collections", verbose_name=_('record writer'))
+    archiver_notes        = TextField(_('archiver notes'))
+    items_done            = CharField(_('items finished'))
+    collector_is_creator  = BooleanField(_('recordist identical to depositor'))
+    is_published          = BooleanField(_('published'))
+    conservation_site     = CharField(_('conservation site'))
+
+    # Technical data
+    media_type            = WeakForeignKey('MediaType', related_name="collections", verbose_name=_('media type'))
+    approx_duration       = DurationField(_('estimated duration'), help_text='hh:mm:ss')
+    physical_items_num    = IntegerField(_('number of components (medium / piece)'))
+    original_format       = WeakForeignKey('OriginalFormat', related_name="collections", verbose_name=_('original format'))
+    physical_format       = WeakForeignKey('PhysicalFormat', related_name="collections", verbose_name=_('archive format'))
+    ad_conversion         = WeakForeignKey('AdConversion', related_name='collections', verbose_name=_('digitization'))
+
+    # No more used old fields
+    alt_ids               = CharField(_('copies (obsolete field)'))
+    travail               = CharField(_('archiver notes (obsolete field)'))
+
+    # All
+    objects               = MediaCollectionManager()
+
+    exclude = ['alt_ids', 'travail']
+
+    class Meta(MetaCore):
+        db_table = 'media_collections'
+        ordering = ['code']
+        verbose_name = _('collection')
+
+    def __unicode__(self):
+        return self.code
+
+    def save(self, force_insert=False, force_update=False, user=None, code=None):
+        super(MediaCollection, self).save(force_insert, force_update)
+
+    @property
+    def public_id(self):
+        return self.code
+
+    @property
+    def has_mediafile(self):
+        "Tell wether this collection has any media files attached to its items"
+        items = self.items.all()
+        for item in items:
+            if item.file:
+                return True
+        return False
+
+    def __name_cmp(self, obj1, obj2):
+        return unaccent_icmp(obj1.name, obj2.name)
+
+    def countries(self):
+        "Return the countries of the items"
+        countries = []
+        for item in self.items.filter(location__isnull=False):
+            for country in item.location.countries():
+                if not country in countries:
+                    countries.append(country)
+        countries.sort(self.__name_cmp)
+        return countries
+    countries.verbose_name = _("states / nations")
+
+    def main_countries(self):
+        "Return the main countries of the items (no aliases or ancestors)"
+        countries = []
+        for item in self.items.filter(location__isnull=False):
+            if not item.location in countries:
+                countries.append(item.location)
+        countries.sort(self.__name_cmp)
+        return countries
+    main_countries.verbose_name = _("states / nations")
+
+    def ethnic_groups(self):
+        "Return the ethnic groups of the items"
+        groups = []
+        items = self.items.all()
+        for item in items:
+            if item.ethnic_group and not item.ethnic_group in groups:
+                groups.append(item.ethnic_group)
+
+        cmp = lambda a, b: unaccent_icmp(a.value, b.value)
+        groups.sort(cmp)
+
+        return groups
+    ethnic_groups.verbose_name = _('populations / social groups')
+
+    def computed_duration(self):
+        duration = Duration()
+        for item in self.items.all():
+            duration += item.computed_duration()
+        return duration
+    computed_duration.verbose_name = _('computed duration')
+
+    def computed_size(self):
+        "Return the total size of a collection"
+        size = 0
+        for item in self.items.all():
+            size += item.size()
+        return size
+    computed_size.verbose_name = _('collection size')
+
+    def document_status(self):
+        if '_I_' in self.public_id:
+            return ugettext('Unpublished')
+        elif '_E_' in self.public_id:
+            return ugettext('Published')
+        else:
+            return ''
+
+    def get_url(self):
+        return get_full_url(reverse('telemeta-collection-detail', kwargs={'public_id':self.pk}))
+
+    def to_dict_with_more(self):
+        # metadata = model_to_dict(self, fields=[], exclude=self.exclude)
+        metadata = self.to_dict()
+        for key in self.exclude:
+            if key in metadata.keys():
+                del metadata[key]
+
+        metadata['url'] = get_full_url(reverse('telemeta-collection-detail', kwargs={'public_id':self.pk}))
+        metadata['doc_status'] = self.document_status()
+        metadata['countries'] = ';'.join([location.name for location in self.main_countries()])
+        metadata['ethnic_groups'] = ';'.join([group.value for group in self.ethnic_groups()])
+        metadata['last_modification_date'] = unicode(self.get_revision().time)
+        metadata['computed_duration'] = unicode(self.computed_duration())
+        metadata['computed_size'] = unicode(self.computed_size())
+        metadata['number_of_items'] = unicode(self.items.all().count())
+
+        i = 0
+        for media in self.related.all():
+            metadata['related_media_title' + '_' + str(i)] = media.title
+            if media.url:
+                tag = 'related_media_url' + '_' + str(i)
+                metadata[tag] = media.url
+            elif media.url:
+                metadata[tag] = get_full_url(reverse('telemeta-collection-related',
+                                            kwargs={'public_id': self.public_id, 'media_id': media.id}))
+            i += 1
+
+        # One ID only
+        identifiers = self.identifiers.all()
+        if identifiers:
+            identifier = identifiers[0]
+            metadata['identifier_id'] = identifier.identifier
+            metadata['identifier_type'] = identifier.type
+            metadata['identifier_date'] = unicode(identifier.date_last)
+            metadata['identifier_note'] = identifier.notes
+
+        # All IDs
+        # i = 0
+        # for indentifier in self.identifiers.all():
+        #     metadata['identifier' + '_' + str(i)] = identifier.identifier
+        #     metadata['identifier_type' + '_' + str(i)] = identifier.type
+        #     metadata['identifier_date_last' + '_' + str(i)] = unicode(identifier.date_last)
+        #     metadata['identifier_notes' + '_' + str(i)] = identifier.notes
+        #     i += 1
+
+        return metadata
+
+
+class MediaCollectionRelated(MediaRelated):
+    "Collection related media"
+
+    collection      = ForeignKey('MediaCollection', related_name="related", verbose_name=_('collection'))
+
+    class Meta(MetaCore):
+        db_table = 'media_collection_related'
+        verbose_name = _('collection related media')
+        verbose_name_plural = _('collection related media')
+
+
+class MediaCollectionIdentifier(Identifier):
+    """Collection identifier"""
+
+    collection = ForeignKey(MediaCollection, related_name="identifiers", verbose_name=_('collection'))
+
+    class Meta(MetaCore):
+        db_table = 'media_collection_identifier'
+        verbose_name = _('collection identifier')
+        verbose_name_plural = _('collection identifiers')
+        unique_together = ('identifier', 'collection')
+
index c92ebf892e9b0d42c33a58fde71d46d33d1f0add..7500bf42aaca2f4230db722d6eaf41294f2fb571 100644 (file)
 # Authors: Olivier Guilyardi <olivier@samalyse.com>
 #          Guillaume Pellerin <yomguy@parisson.com>
 
-__all__ = ['ModelCore', 'MetaCore', 'DurationField', 'Duration', 'WeakForeignKey',
-           'EnhancedModel', 'CharField', 'TextField', 'IntegerField', 'BooleanField',
-           'DateTimeField', 'FileField', 'ForeignKey', 'FloatField', 'DateField',
-           'RequiredFieldError', 'CoreQuerySet', 'CoreManager', 'word_search_q']
 
-from django.core import exceptions
+import datetime
+import mimetypes
+import re, os, random
+
 from django import forms
-from xml.dom.minidom import getDOMImplementation
-from django.db.models.fields import FieldDoesNotExist
-from django.db.models import Q
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.contrib.sites.models import Site
+from django.core import exceptions
+from django.core.exceptions import ObjectDoesNotExist, ValidationError
+from django.core.urlresolvers import reverse, reverse_lazy
 from django.db import models
-import datetime
+from django.db.models import Q, URLField
+from django.db.models.fields import FieldDoesNotExist
+from django.forms.models import model_to_dict
+from django.utils.translation import ugettext
 from django.utils.translation import ugettext_lazy as _
-import re
-from django.core.exceptions import ObjectDoesNotExist
-from south.modelsinspector import add_introspection_rules
-
-
-class Duration(object):
-    """Represent a time duration"""
-    def __init__(self, *args, **kwargs):
-        if len(args) and isinstance(args[0], datetime.timedelta):
-            self._delta = datetime.timedelta(days=args[0].days, seconds=args[0].seconds)
-        else:
-            self._delta = datetime.timedelta(*args, **kwargs)
-
-    def __decorate(self, method, other):
-        if isinstance(other, Duration):
-            res = method(other._delta)
-        else:
-            res = method(other)
-        if type(res) == datetime.timedelta:
-            return Duration(res)
-
-        return res
-
-    def __add__(self, other):
-        return self.__decorate(self._delta.__add__, other)
-
-    def __nonzero__(self):
-        return self._delta.__nonzero__()
-
-    def __str__(self):
-        hours   = self._delta.days * 24 + self._delta.seconds / 3600
-        minutes = (self._delta.seconds % 3600) / 60
-        seconds = self._delta.seconds % 60
-
-        return "%.2d:%.2d:%.2d" % (hours, minutes, seconds)
-
-    @staticmethod
-    def fromstr(str):
-        if not str:
-            return Duration()
-
-        test = re.match('^([0-9]+)(?::([0-9]+)(?::([0-9]+))?)?$', str)
-        if test:
-            groups = test.groups()
-            try:
-                hours = minutes = seconds = 0
-                if groups[0]:
-                    hours = int(groups[0])
-                    if groups[1]:
-                        minutes = int(groups[1])
-                        if groups[2]:
-                            seconds = int(groups[2])
-
-                return Duration(hours=hours, minutes=minutes, seconds=seconds)
-            except TypeError:
-                print groups
-                raise
-        else:
-            raise ValueError("Malformed duration string: " + str)
-
-    def as_seconds(self):
-        return self._delta.days * 24 * 3600 + self._delta.seconds
-
-
-def normalize_field(args, default_value=None):
-    """Normalize field constructor arguments, so that the field is marked blank=True
-       and has a default value by default.
-
-       This behaviour can be disabled by passing the special argument required=True.
-
-       The default value can also be overriden with the default=value argument.
-       """
-    required = False
-    if args.has_key('required'):
-        required = args['required']
-        args.pop('required')
-
-    args['blank'] = not required
-
-    if not required:
-        if not args.has_key('default'):
-            if args.get('null'):
-                args['default'] = None
-            elif default_value is not None:
-                args['default'] = default_value
-
-    return args
-
-
-class DurationField(models.Field):
-    """Duration Django model field based on Django TimeField.
-    Essentially the same as a TimeField, but with values over 24h allowed.
-
-    The constructor arguments are also normalized with normalize_field().
-    """
-
-    description = _("Duration")
-
-    __metaclass__ = models.SubfieldBase
-
-    default_error_messages = {
-        'invalid': _('Enter a valid duration in HH:MM[:ss] format.'),
-    }
-
-    def __init__(self, *args, **kwargs):
-        super(DurationField, self).__init__(*args, **normalize_field(kwargs, '0'))
-
-    def db_type(self):
-        return 'int'
-
-    def to_python(self, value):
-        if value is None:
-            return None
-        if isinstance(value, int) or isinstance(value, long):
-            return Duration(seconds=value)
-        if isinstance(value, datetime.time):
-            return Duration(hours=value.hour, minutes=value.minute, seconds=value.second)
-        if isinstance(value, datetime.datetime):
-            # Not usually a good idea to pass in a datetime here (it loses
-            # information), but this can be a side-effect of interacting with a
-            # database backend (e.g. Oracle), so we'll be accommodating.
-            return self.to_python(value.time())
-        else:
-            value = str(value)
-        try:
-            return Duration.fromstr(value)
-        except ValueError:
-            raise exceptions.ValidationError(self.error_messages['invalid'])
-
-    def get_prep_value(self, value):
-        return self.to_python(value)
-
-    def get_db_prep_value(self, value, connection=None, prepared=False):
-        # Casts times into the format expected by the backend
-        try:
-            return value.as_seconds()
-        except:
-            return value
-
-    def value_to_string(self, obj):
-        val = self._get_val_from_obj(obj)
-        if val is None:
-            data = ''
-        else:
-            data = unicode(val)
-        return data
-
-    def formfield(self, **kwargs):
-        defaults = {'form_class': forms.CharField}
-        defaults.update(kwargs)
-        return super(DurationField, self).formfield(**defaults)
-
-
-class ForeignKey(models.ForeignKey):
-    """The constructor arguments of this ForeignKey are normalized
-    with normalize_field(), however the field is marked required by default
-    unless it is allowed to be null."""
+from telemeta.models.utils import *
+from telemeta.models.fields import *
+from telemeta.util.kdenlive.session import *
+from telemeta.util.unaccent import unaccent_icmp
+from xml.dom.minidom import getDOMImplementation
 
-    def __init__(self, to, **kwargs):
-        if not kwargs.has_key('required'):
-            if not kwargs.get('null'):
-                kwargs['required'] = True
 
-        super(ForeignKey, self).__init__(to, **normalize_field(kwargs, 0))
+PUBLIC_ACCESS_CHOICES = (('none', _('none')), ('metadata', _('metadata')),
+                         ('mixed', _('mixed')), ('full', _('full')))
 
+mimetypes.add_type('video/webm','.webm')
 
-class WeakForeignKey(ForeignKey):
-    """A weak foreign key is the same as foreign key but without cascading
-    delete. Instead the reference is set to null when the referenced record
-    get deleted. This emulates the ON DELETE SET NULL sql behaviour.
+app_name = 'telemeta'
 
-    This field is automatically allowed to be null, there's no need to pass
-    null=True.
+strict_code = getattr(settings, 'TELEMETA_STRICT_CODE', False)
 
-    The constructor arguments are normalized with normalize_field() by the
-    parent ForeignKey
 
-    Warning: must be used in conjunction with EnhancedQuerySet, EnhancedManager,
-    and EnhancedModel
-    """
-    def __init__(self, to, **kwargs):
-        kwargs['null'] = True
-        super(WeakForeignKey, self).__init__(to, **kwargs)
 
 
 class EnhancedQuerySet(models.query.QuerySet):
@@ -278,79 +117,6 @@ class EnhancedModel(models.Model):
         abstract = True
 
 
-class CharField(models.CharField):
-    """This is a CharField with a default max_length of 250.
-
-       The arguments are also normalized with normalize_field()"""
-
-    def __init__(self, *args, **kwargs):
-        if not kwargs.has_key('max_length'):
-            kwargs['max_length'] = 250
-        super(CharField, self).__init__(*args, **normalize_field(kwargs, ''))
-
-
-class IntegerField(models.IntegerField):
-    """IntegerField normalized with normalize_field()"""
-
-    def __init__(self, *args, **kwargs):
-        super(IntegerField, self).__init__(*args, **normalize_field(kwargs, 0))
-
-
-class BooleanField(models.BooleanField):
-    """BooleanField normalized with normalize_field()"""
-
-    def __init__(self, *args, **kwargs):
-        super(BooleanField, self).__init__(*args, **normalize_field(kwargs, False))
-
-
-class TextField(models.TextField):
-    """TextField normalized with normalize_field()"""
-
-    def __init__(self, *args, **kwargs):
-        super(TextField, self).__init__(*args, **normalize_field(kwargs, ''))
-
-
-class DateTimeField(models.DateTimeField):
-    """DateTimeField normalized with normalize_field(). This field is allowed to
-    be null by default unless null=False is passed"""
-
-    def __init__(self, *args, **kwargs):
-        if not kwargs.has_key('null'):
-            kwargs['null'] = True
-        super(DateTimeField, self).__init__(*args, **normalize_field(kwargs))
-
-
-class FileField(models.FileField):
-    """FileField normalized with normalize_field()"""
-
-    def __init__(self, *args, **kwargs):
-        super(FileField, self).__init__(*args, **normalize_field(kwargs, ''))
-
-
-class FloatField(models.FloatField):
-    """FloatField normalized with normalize_field()"""
-
-    def __init__(self, *args, **kwargs):
-        super(FloatField, self).__init__(*args, **normalize_field(kwargs, 0))
-
-
-class DateField(models.DateField):
-    """DateField normalized with normalize_field(). This field is allowed to
-    be null by default unless null=False is passed"""
-
-    def __init__(self, *args, **kwargs):
-        if not kwargs.has_key('null'):
-            kwargs['null'] = True
-        super(DateField, self).__init__(*args, **normalize_field(kwargs))
-
-
-class RequiredFieldError(Exception):
-    def __init__(self, model, field):
-        self.model = model
-        self.field = field
-        super(Exception, self).__init__('%s.%s is required' % (model._meta.object_name, field.name))
-
-
 class ModelCore(EnhancedModel):
 
     @classmethod
@@ -435,16 +201,6 @@ class MetaCore:
     app_label = 'telemeta'
 
 
-def word_search_q(field, pattern):
-    words = re.split("[ .*-]+", pattern)
-    q = Q()
-    for w in words:
-        if len(w) >= 3:
-            kwargs = {field + '__icontains': w}
-            q &= Q(**kwargs)
-
-    return q
-
 
 class CoreQuerySet(EnhancedQuerySet):
     "Base class for all query sets"
@@ -496,16 +252,3 @@ class CoreManager(EnhancedManager):
         return super(CoreManager, self).get(**kwargs)
 
 
-# South introspection rules
-add_introspection_rules([], ["^telemeta\.models\.core\.CharField"])
-add_introspection_rules([], ["^telemeta\.models\.core\.TextField"])
-add_introspection_rules([], ["^telemeta\.models\.core\.FileField"])
-add_introspection_rules([], ["^telemeta\.models\.core\.IntegerField"])
-add_introspection_rules([], ["^telemeta\.models\.core\.BooleanField"])
-add_introspection_rules([], ["^telemeta\.models\.core\.DateTimeField"])
-add_introspection_rules([], ["^telemeta\.models\.core\.DateField"])
-add_introspection_rules([], ["^telemeta\.models\.core\.FloatField"])
-add_introspection_rules([], ["^telemeta\.models\.core\.DurationField"])
-add_introspection_rules([], ["^telemeta\.models\.core\.ForeignKey"])
-add_introspection_rules([], ["^telemeta\.models\.core\.WeakForeignKey"])
-
diff --git a/telemeta/models/corpus.py b/telemeta/models/corpus.py
new file mode 100644 (file)
index 0000000..067b82a
--- /dev/null
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2010 Samalyse SARL
+# Copyright (C) 2010-2014 Parisson SARL
+
+# This software is a computer program whose purpose is to backup, analyse,
+# transcode and stream any audio content with its metadata over a web frontend.
+
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+#
+# Authors: Olivier Guilyardi <olivier@samalyse.com>
+#          David LIPSZYC <davidlipszyc@gmail.com>
+#          Guillaume Pellerin <yomguy@parisson.com>
+
+from __future__ import division
+from django.utils.translation import ugettext_lazy as _
+from telemeta.models.core import *
+from telemeta.models.resource import *
+from telemeta.models.collection import *
+
+
+class MediaCorpus(MediaBaseResource):
+    "Describe a corpus"
+
+    element_type = 'corpus'
+    children_type = 'collections'
+
+    children = models.ManyToManyField(MediaCollection, related_name="corpus",
+                                      verbose_name=_('collections'),  blank=True, null=True)
+    recorded_from_year    = IntegerField(_('recording year (from)'), help_text=_('YYYY'))
+    recorded_to_year      = IntegerField(_('recording year (until)'), help_text=_('YYYY'))
+
+    objects = MediaCorpusManager()
+
+    @property
+    def public_id(self):
+        return self.code
+
+    @property
+    def has_mediafile(self):
+        for child in self.children.all():
+            if child.has_mediafile:
+                return True
+        return False
+
+    def computed_duration(self):
+        duration = Duration()
+        for child in self.children.all():
+            duration += child.computed_duration()
+        return duration
+    computed_duration.verbose_name = _('total available duration')
+
+    class Meta(MetaCore):
+        db_table = 'media_corpus'
+        verbose_name = _('corpus')
+        verbose_name_plural = _('corpus')
+        ordering = ['code']
+
+
+class MediaCorpusRelated(MediaRelated):
+    "Corpus related media"
+
+    resource = ForeignKey(MediaCorpus, related_name="related", verbose_name=_('corpus'))
+
+    class Meta(MetaCore):
+        db_table = 'media_corpus_related'
+        verbose_name = _('corpus related media')
+        verbose_name_plural = _('corpus related media')
+
index 504121c0b8b4e4abede3dec1d2c428b587a89180..964162d74620f132546d0fdf7aef5ab522c20b82 100644 (file)
@@ -32,8 +32,9 @@
 #
 # Author: Olivier Guilyardi <olivier@samalyse.com>
 
-from telemeta.models.core import Duration
-from telemeta.models.media import *
+from telemeta.models.core import *
+from telemeta.models.item import *
+from telemeta.models.collection import *
 from django.contrib.sites.models import Site
 from django.conf import settings
 
diff --git a/telemeta/models/fields.py b/telemeta/models/fields.py
new file mode 100644 (file)
index 0000000..9752e2a
--- /dev/null
@@ -0,0 +1,319 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007-2010 Samalyse SARL
+# Copyright (C) 2010-2015 Parisson SARL
+#
+# This software is a computer program whose purpose is to backup, analyse,
+# transcode and stream any audio content with its metadata over a web frontend.
+#
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+#
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+#
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+#
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+#
+# Authors: Olivier Guilyardi <olivier@samalyse.com>
+#          Guillaume Pellerin <yomguy@parisson.com>
+
+from __future__ import division
+
+__all__ = ['DurationField', 'Duration', 'WeakForeignKey',
+           'CharField', 'TextField', 'IntegerField', 'BooleanField',
+           'DateTimeField', 'FileField', 'ForeignKey', 'FloatField', 'DateField',
+           'RequiredFieldError',]
+
+import datetime
+from django import forms
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+from south.modelsinspector import add_introspection_rules
+
+
+class Duration(object):
+    """Represent a time duration"""
+    def __init__(self, *args, **kwargs):
+        if len(args) and isinstance(args[0], datetime.timedelta):
+            self._delta = datetime.timedelta(days=args[0].days, seconds=args[0].seconds)
+        else:
+            self._delta = datetime.timedelta(*args, **kwargs)
+
+    def __decorate(self, method, other):
+        if isinstance(other, Duration):
+            res = method(other._delta)
+        else:
+            res = method(other)
+        if type(res) == datetime.timedelta:
+            return Duration(res)
+
+        return res
+
+    def __add__(self, other):
+        return self.__decorate(self._delta.__add__, other)
+
+    def __nonzero__(self):
+        return self._delta.__nonzero__()
+
+    def __str__(self):
+        hours   = self._delta.days * 24 + self._delta.seconds / 3600
+        minutes = (self._delta.seconds % 3600) / 60
+        seconds = self._delta.seconds % 60
+
+        return "%.2d:%.2d:%.2d" % (hours, minutes, seconds)
+
+    @staticmethod
+    def fromstr(str):
+        if not str:
+            return Duration()
+
+        test = re.match('^([0-9]+)(?::([0-9]+)(?::([0-9]+))?)?$', str)
+        if test:
+            groups = test.groups()
+            try:
+                hours = minutes = seconds = 0
+                if groups[0]:
+                    hours = int(groups[0])
+                    if groups[1]:
+                        minutes = int(groups[1])
+                        if groups[2]:
+                            seconds = int(groups[2])
+
+                return Duration(hours=hours, minutes=minutes, seconds=seconds)
+            except TypeError:
+                print groups
+                raise
+        else:
+            raise ValueError("Malformed duration string: " + str)
+
+    def as_seconds(self):
+        return self._delta.days * 24 * 3600 + self._delta.seconds
+
+
+def normalize_field(args, default_value=None):
+    """Normalize field constructor arguments, so that the field is marked blank=True
+       and has a default value by default.
+
+       This behaviour can be disabled by passing the special argument required=True.
+
+       The default value can also be overriden with the default=value argument.
+       """
+    required = False
+    if args.has_key('required'):
+        required = args['required']
+        args.pop('required')
+
+    args['blank'] = not required
+
+    if not required:
+        if not args.has_key('default'):
+            if args.get('null'):
+                args['default'] = None
+            elif default_value is not None:
+                args['default'] = default_value
+
+    return args
+
+
+class DurationField(models.Field):
+    """Duration Django model field based on Django TimeField.
+    Essentially the same as a TimeField, but with values over 24h allowed.
+
+    The constructor arguments are also normalized with normalize_field().
+    """
+
+    description = _("Duration")
+
+    __metaclass__ = models.SubfieldBase
+
+    default_error_messages = {
+        'invalid': _('Enter a valid duration in HH:MM[:ss] format.'),
+    }
+
+    def __init__(self, *args, **kwargs):
+        super(DurationField, self).__init__(*args, **normalize_field(kwargs, '0'))
+
+    def db_type(self):
+        return 'int'
+
+    def to_python(self, value):
+        if value is None:
+            return None
+        if isinstance(value, int) or isinstance(value, long):
+            return Duration(seconds=value)
+        if isinstance(value, datetime.time):
+            return Duration(hours=value.hour, minutes=value.minute, seconds=value.second)
+        if isinstance(value, datetime.datetime):
+            # Not usually a good idea to pass in a datetime here (it loses
+            # information), but this can be a side-effect of interacting with a
+            # database backend (e.g. Oracle), so we'll be accommodating.
+            return self.to_python(value.time())
+        else:
+            value = str(value)
+        try:
+            return Duration.fromstr(value)
+        except ValueError:
+            raise exceptions.ValidationError(self.error_messages['invalid'])
+
+    def get_prep_value(self, value):
+        return self.to_python(value)
+
+    def get_db_prep_value(self, value, connection=None, prepared=False):
+        # Casts times into the format expected by the backend
+        try:
+            return value.as_seconds()
+        except:
+            return value
+
+    def value_to_string(self, obj):
+        val = self._get_val_from_obj(obj)
+        if val is None:
+            data = ''
+        else:
+            data = unicode(val)
+        return data
+
+    def formfield(self, **kwargs):
+        defaults = {'form_class': forms.CharField}
+        defaults.update(kwargs)
+        return super(DurationField, self).formfield(**defaults)
+
+
+class ForeignKey(models.ForeignKey):
+    """The constructor arguments of this ForeignKey are normalized
+    with normalize_field(), however the field is marked required by default
+    unless it is allowed to be null."""
+
+    def __init__(self, to, **kwargs):
+        if not kwargs.has_key('required'):
+            if not kwargs.get('null'):
+                kwargs['required'] = True
+
+        super(ForeignKey, self).__init__(to, **normalize_field(kwargs, 0))
+
+
+class WeakForeignKey(ForeignKey):
+    """A weak foreign key is the same as foreign key but without cascading
+    delete. Instead the reference is set to null when the referenced record
+    get deleted. This emulates the ON DELETE SET NULL sql behaviour.
+
+    This field is automatically allowed to be null, there's no need to pass
+    null=True.
+
+    The constructor arguments are normalized with normalize_field() by the
+    parent ForeignKey
+
+    Warning: must be used in conjunction with EnhancedQuerySet, EnhancedManager,
+    and EnhancedModel
+    """
+    def __init__(self, to, **kwargs):
+        kwargs['null'] = True
+        super(WeakForeignKey, self).__init__(to, **kwargs)
+
+
+
+class CharField(models.CharField):
+    """This is a CharField with a default max_length of 250.
+
+       The arguments are also normalized with normalize_field()"""
+
+    def __init__(self, *args, **kwargs):
+        if not kwargs.has_key('max_length'):
+            kwargs['max_length'] = 250
+        super(CharField, self).__init__(*args, **normalize_field(kwargs, ''))
+
+
+class IntegerField(models.IntegerField):
+    """IntegerField normalized with normalize_field()"""
+
+    def __init__(self, *args, **kwargs):
+        super(IntegerField, self).__init__(*args, **normalize_field(kwargs, 0))
+
+
+class BooleanField(models.BooleanField):
+    """BooleanField normalized with normalize_field()"""
+
+    def __init__(self, *args, **kwargs):
+        super(BooleanField, self).__init__(*args, **normalize_field(kwargs, False))
+
+
+class TextField(models.TextField):
+    """TextField normalized with normalize_field()"""
+
+    def __init__(self, *args, **kwargs):
+        super(TextField, self).__init__(*args, **normalize_field(kwargs, ''))
+
+
+class DateTimeField(models.DateTimeField):
+    """DateTimeField normalized with normalize_field(). This field is allowed to
+    be null by default unless null=False is passed"""
+
+    def __init__(self, *args, **kwargs):
+        if not kwargs.has_key('null'):
+            kwargs['null'] = True
+        super(DateTimeField, self).__init__(*args, **normalize_field(kwargs))
+
+
+class FileField(models.FileField):
+    """FileField normalized with normalize_field()"""
+
+    def __init__(self, *args, **kwargs):
+        super(FileField, self).__init__(*args, **normalize_field(kwargs, ''))
+
+
+class FloatField(models.FloatField):
+    """FloatField normalized with normalize_field()"""
+
+    def __init__(self, *args, **kwargs):
+        super(FloatField, self).__init__(*args, **normalize_field(kwargs, 0))
+
+
+class DateField(models.DateField):
+    """DateField normalized with normalize_field(). This field is allowed to
+    be null by default unless null=False is passed"""
+
+    def __init__(self, *args, **kwargs):
+        if not kwargs.has_key('null'):
+            kwargs['null'] = True
+        super(DateField, self).__init__(*args, **normalize_field(kwargs))
+
+
+class RequiredFieldError(Exception):
+    def __init__(self, model, field):
+        self.model = model
+        self.field = field
+        super(Exception, self).__init__('%s.%s is required' % (model._meta.object_name, field.name))
+
+
+
+# South introspection rules
+add_introspection_rules([], ["^telemeta\.models\.core\.CharField"])
+add_introspection_rules([], ["^telemeta\.models\.core\.TextField"])
+add_introspection_rules([], ["^telemeta\.models\.core\.FileField"])
+add_introspection_rules([], ["^telemeta\.models\.core\.IntegerField"])
+add_introspection_rules([], ["^telemeta\.models\.core\.BooleanField"])
+add_introspection_rules([], ["^telemeta\.models\.core\.DateTimeField"])
+add_introspection_rules([], ["^telemeta\.models\.core\.DateField"])
+add_introspection_rules([], ["^telemeta\.models\.core\.FloatField"])
+add_introspection_rules([], ["^telemeta\.models\.core\.DurationField"])
+add_introspection_rules([], ["^telemeta\.models\.core\.ForeignKey"])
+add_introspection_rules([], ["^telemeta\.models\.core\.WeakForeignKey"])
+
diff --git a/telemeta/models/fonds.py b/telemeta/models/fonds.py
new file mode 100644 (file)
index 0000000..69faa6b
--- /dev/null
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2010 Samalyse SARL
+# Copyright (C) 2010-2014 Parisson SARL
+
+# This software is a computer program whose purpose is to backup, analyse,
+# transcode and stream any audio content with its metadata over a web frontend.
+
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+#
+# Authors: Olivier Guilyardi <olivier@samalyse.com>
+#          David LIPSZYC <davidlipszyc@gmail.com>
+#          Guillaume Pellerin <yomguy@parisson.com>
+
+from __future__ import division
+from django.utils.translation import ugettext_lazy as _
+from telemeta.models.core import *
+from telemeta.models.resource import *
+from telemeta.models.corpus import *
+
+
+class MediaFonds(MediaBaseResource):
+    "Describe fonds"
+
+    element_type = 'fonds'
+    children_type = 'corpus'
+
+    children = models.ManyToManyField(MediaCorpus, related_name="fonds",
+                                      verbose_name=_('corpus'), blank=True, null=True)
+
+    objects = MediaFondsManager()
+
+    @property
+    def public_id(self):
+        return self.code
+
+    @property
+    def has_mediafile(self):
+        for child in self.children.all():
+            if child.has_mediafile:
+                return True
+        return False
+
+    def computed_duration(self):
+        duration = Duration()
+        for child in self.children.all():
+            duration += child.computed_duration()
+        return duration
+    computed_duration.verbose_name = _('total available duration')
+
+    class Meta(MetaCore):
+        db_table = 'media_fonds'
+        verbose_name = _('fonds')
+        verbose_name_plural = _('fonds')
+        ordering = ['code']
+
+
+class MediaFondsRelated(MediaRelated):
+    "Fonds related media"
+
+    resource = ForeignKey(MediaFonds, related_name="related", verbose_name=_('fonds'))
+
+    class Meta(MetaCore):
+        db_table = 'media_fonds_related'
+        verbose_name = _('fonds related media')
+        verbose_name_plural = _('fonds related media')
+
+
index e4b4a991dd2287925d8b172509055b7144ea6d46..dc050995bebec7e740bfa887bbf617f935805ae1 100644 (file)
@@ -40,13 +40,8 @@ from django.utils.translation import ugettext_lazy as _
 from django.core.exceptions import ValidationError
 from telemeta.models.core import *
 from telemeta.util.unaccent import unaccent_icmp
-from telemeta.models.location import *
-from telemeta.models.system import *
-from telemeta.models.query import *
-from telemeta.models.instrument import *
 from telemeta.models.enum import *
 from telemeta.models.language import *
-from telemeta.models.media import *
 from django.db import models
 
 
diff --git a/telemeta/models/identifier.py b/telemeta/models/identifier.py
new file mode 100644 (file)
index 0000000..47fc315
--- /dev/null
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2010 Samalyse SARL
+# Copyright (C) 2010-2014 Parisson SARL
+
+# This software is a computer program whose purpose is to backup, analyse,
+# transcode and stream any audio content with its metadata over a web frontend.
+
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+#
+# Authors: Olivier Guilyardi <olivier@samalyse.com>
+#          David LIPSZYC <davidlipszyc@gmail.com>
+#          Guillaume Pellerin <yomguy@parisson.com>
+
+
+from telemeta.models.core import *
+from telemeta.models.fields import *
+from django.utils.translation import ugettext_lazy as _
+
+
+class Identifier(ModelCore):
+    """Resource identifier"""
+
+    identifier = CharField(_('identifier'), max_length=255, blank=True, unique=True)
+    type = WeakForeignKey('IdentifierType', verbose_name=_('type'))
+    date_add = DateTimeField(_('date added'), auto_now_add=True)
+    date_first = DateTimeField(_('date of first attribution'))
+    date_last = DateTimeField(_('date of last attribution'))
+    date_modified = DateTimeField(_('date modified'), auto_now=True)
+    notes = TextField(_('notes'))
+
+    class Meta(MetaCore):
+        abstract = True
+        ordering = ['-date_last']
index b93db1d83f8148f61867661ac04aea2c67a79c27..8de13fdf6d0db0900ff0fe62e33ce63732a36bd0 100644 (file)
@@ -38,6 +38,7 @@
 from telemeta.models.core import *
 from django.utils.translation import ugettext_lazy as _
 
+
 class Instrument(ModelCore):
     "Instrument used in the item"
     name = CharField(_('name'), required=True)
diff --git a/telemeta/models/item.py b/telemeta/models/item.py
new file mode 100644 (file)
index 0000000..db2417a
--- /dev/null
@@ -0,0 +1,492 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2010 Samalyse SARL
+# Copyright (C) 2010-2014 Parisson SARL
+
+# This software is a computer program whose purpose is to backup, analyse,
+# transcode and stream any audio content with its metadata over a web frontend.
+
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+#
+# Authors: Olivier Guilyardi <olivier@samalyse.com>
+#          David LIPSZYC <davidlipszyc@gmail.com>
+#          Guillaume Pellerin <yomguy@parisson.com>
+
+from __future__ import division
+from django.utils.translation import ugettext_lazy as _
+from telemeta.models.core import *
+from telemeta.models.resource import *
+from telemeta.models.query import *
+from telemeta.models.identifier import *
+from telemeta.models.resource import *
+from telemeta.models.enum import *
+
+
+item_published_code_regex = getattr(settings, 'ITEM_PUBLISHED_CODE_REGEX', '[A-Za-z0-9._-]*')
+item_unpublished_code_regex = getattr(settings, 'ITEM_UNPUBLISHED_CODE_REGEX', '[A-Za-z0-9._-]*')
+
+# CREM
+# item_published_code_regex    = collection_published_code_regex + '(?:_[0-9]{2,3}){1,2}'
+# item_unpublished_code_regex  = collection_unpublished_code_regex + '_[0-9]{2,3}(?:_[0-9]{2,3}){0,2}'
+
+item_code_regex = '(?:%s|%s)' % (item_published_code_regex, item_unpublished_code_regex)
+
+ITEM_PUBLIC_ACCESS_CHOICES = (('none', _('none')), ('metadata', _('metadata')),
+                         ('full', _('full')))
+
+ITEM_TRANSODING_STATUS = ((0, _('broken')), (1, _('pending')), (2, _('processing')),
+                         (3, _('done')), (5, _('ready')))
+
+
+class MediaItem(MediaResource):
+    "Describe an item"
+
+    element_type = 'item'
+
+    # Main Informations
+    title                 = CharField(_('title'))
+    alt_title             = CharField(_('original title / translation'))
+    collector             = CharField(_('collector'), help_text=_('First name, Last name ; First name, Last name'))
+    collection            = ForeignKey('MediaCollection', related_name="items", verbose_name=_('collection'))
+    recorded_from_date    = DateField(_('recording date (from)'), help_text=_('YYYY-MM-DD'))
+    recorded_to_date      = DateField(_('recording date (until)'), help_text=_('YYYY-MM-DD'))
+    public_access         = CharField(_('access type'), choices=ITEM_PUBLIC_ACCESS_CHOICES, max_length=16, default="metadata")
+
+    # Geographic and cultural informations
+    location              = WeakForeignKey('Location', verbose_name=_('location'))
+    location_comment      = CharField(_('location details'))
+    cultural_area         = CharField(_('cultural area'))
+    language              = CharField(_('language'))
+    language_iso          = ForeignKey('Language', related_name="items", verbose_name=_('Language (ISO norm)'), blank=True, null=True, on_delete=models.SET_NULL)
+    ethnic_group          = WeakForeignKey('EthnicGroup', related_name="items", verbose_name=_('population / social group'))
+    context_comment       = TextField(_('Ethnographic context'))
+
+    # Musical informations
+    moda_execut           = CharField(_('implementing rules'))
+    vernacular_style      = WeakForeignKey('VernacularStyle', related_name="items", verbose_name=_('vernacular style'))
+    generic_style         = WeakForeignKey('GenericStyle', related_name="items", verbose_name=_('generic style'))
+    author                = CharField(_('author / compositor'), help_text=_('First name, Last name ; First name, Last name'))
+
+    # Legal mentions
+    organization          = WeakForeignKey('Organization', verbose_name=_('organization'))
+    depositor             = CharField(_('depositor'))
+    rights                = WeakForeignKey('Rights', verbose_name=_('rights'))
+
+    # Archiving data
+    code                  = CharField(_('code'), unique=True, blank=True, required=True, help_text=_('CollectionCode_ItemCode'))
+    old_code              = CharField(_('original code'), unique=False, blank=True)
+    track                 = CharField(_('item number'))
+    collector_selection   = CharField(_('collector selection'))
+    collector_from_collection = BooleanField(_('collector as in collection'))
+    creator_reference     = CharField(_('creator reference'))
+    external_references   = TextField(_('published references'))
+    auto_period_access    = BooleanField(_('automatic access after a rolling period'), default=True)
+    comment               = TextField(_('remarks'))
+
+    # Technical data
+    media_type            = WeakForeignKey('MediaType', related_name="items", verbose_name=_('media type'))
+    approx_duration       = DurationField(_('approximative duration'), blank=True, help_text=_('hh:mm:ss'))
+    mimetype              = CharField(_('mime type'), max_length=255, blank=True)
+    file                  = FileField(_('file'), upload_to='items/%Y/%m/%d', db_column="filename", max_length=1024)
+    url                   = URLField(_('URL'), max_length=512, blank=True)
+
+    # LAM
+    recordist             = CharField(_('recordist'))
+    digitalist            = CharField(_('digitalist'))
+    digitization_date     = DateField(_('digitization date'))
+    publishing_date       = DateField(_('publishing date'))
+    scientist             = CharField(_('scientist'), help_text=_('First name, Last name ; First name, Last name'))
+    topic                 = WeakForeignKey('Topic', verbose_name=_('topic'))
+    summary               = TextField(_('summary'))
+    contributor           = CharField(_('contributor'))
+
+    # Manager
+    objects               = MediaItemManager()
+
+    exclude = ['copied_from_item', 'mimetype', 'url',
+                    'organization', 'depositor', 'rights',
+                    'recordist', 'digitalist', 'digitization_date',
+                    'publishing_date', 'scientist', 'topic',
+                    'summary', 'contributor', ]
+
+    def keywords(self):
+        return ContextKeyword.objects.filter(item_relations__item = self)
+    keywords.verbose_name = _('keywords')
+
+    @property
+    def public_id(self):
+        if self.code:
+            return self.code
+        return str(self.id)
+
+    @property
+    def mime_type(self):
+        if not self.mimetype:
+            if self.file:
+                if os.path.exists(self.file.path):
+                    self.mimetype = mimetypes.guess_type(self.file.path)[0]
+                    self.save()
+                    return self.mimetype
+                else:
+                    return 'none'
+            else:
+                return 'none'
+        else:
+            return _('none')
+
+
+    class Meta(MetaCore):
+        db_table = 'media_items'
+        permissions = (("can_play_all_items", "Can play all media items"),
+                       ("can_download_all_items", "Can download all media items"), )
+        verbose_name = _('item')
+
+    def is_valid_code(self, code):
+        "Check if the item code is well formed"
+        if not re.match('^' + self.collection.code, self.code):
+            return False
+        if self.collection.is_published:
+            regex = '^' + item_published_code_regex + '$'
+        else:
+            regex = '^' + item_unpublished_code_regex + '$'
+        if re.match(regex, code):
+            return True
+        return False
+
+    def clean(self):
+        if strict_code:
+            if self.code and not self.is_valid_code(self.code):
+                raise ValidationError("%s is not a valid item code for collection %s"
+                                            % (self.code, self.collection.code))
+
+    def save(self, force_insert=False, force_update=False):
+        super(MediaItem, self).save(force_insert, force_update)
+
+    def computed_duration(self):
+        "Tell the length in seconds of this item media data"
+        return self.approx_duration
+
+    computed_duration.verbose_name = _('computed duration')
+
+    def __unicode__(self):
+        if self.title and not re.match('^ *N *$', self.title):
+            title = self.title
+        else:
+            title = unicode(self.collection.title)
+        if self.track:
+            title += ' ' + self.track
+        return title
+
+    def get_source(self):
+        source = None
+        if self.file and os.path.exists(self.file.path):
+            source = self.file.path
+        elif self.url:
+            source = self.url
+        return source
+
+    @property
+    def instruments(self):
+        "Return the instruments of the item"
+        instruments = []
+        performances = MediaItemPerformance.objects.filter(media_item=self)
+        for performance in performances:
+            instrument = performance.instrument
+            alias = performance.alias
+            if not instrument in instruments:
+                instruments.append(instrument)
+            if not alias in instruments:
+                instruments.append(alias)
+
+        instruments.sort(self.__name_cmp)
+        return instruments
+
+        instruments.verbose_name = _("instruments")
+
+    def size(self):
+        if self.file and os.path.exists(self.file.path):
+            return self.file.size
+        else:
+            return 0
+    size.verbose_name = _('item size')
+
+    def get_url(self):
+        return get_full_url(reverse('telemeta-item-detail', kwargs={'public_id':self.pk}))
+
+    def to_dict_with_more(self):
+        # metadata = model_to_dict(self, fields=[], exclude=self.exclude)
+        metadata = self.to_dict()
+        for key in self.exclude:
+            if key in metadata.keys():
+                del metadata[key]
+
+        metadata['url'] = self.get_url()
+        metadata['last_modification_date'] = unicode(self.get_revision().time)
+        metadata['collection'] = self.collection.get_url()
+
+        keywords = []
+        for keyword in self.keywords():
+            keywords.append(keyword.value)
+        metadata['keywords'] = ';'.join(keywords)
+
+        i = 0
+        for media in self.related.all():
+            if media.title:
+                tag = 'related_media_title' + '_' + str(i)
+                metadata[tag] = media.title
+            else:
+                metadata[tag] = ''
+            if media.url:
+                tag = 'related_media_url' + '_' + str(i)
+                metadata[tag] = media.url
+            elif media.url:
+                metadata[tag] = get_full_url(reverse('telemeta-collection-related',
+                                            kwargs={'public_id': self.public_id, 'media_id': media.id}))
+            i += 1
+
+
+        instruments = []
+        instrument_vernacular_names = []
+        performers = []
+
+        for performance in self.performances.all():
+            if performance.instrument:
+                instruments.append(performance.instrument.name)
+            if performance.alias:
+                instrument_vernacular_names.append(performance.alias.name)
+            if performance.musicians:
+                performers.append(performance.musicians.replace(' et ', ';'))
+
+        metadata['instruments'] = ';'.join(instruments)
+        metadata['instrument_vernacular_names'] = ';'.join(instrument_vernacular_names)
+        metadata['performers'] = ';'.join(performers)
+
+        i = 0
+        for indentifier in self.identifiers.all():
+            metadata['identifier' + '_' + str(i)] = identifier.identifier
+            metadata['identifier_type' + '_' + str(i)] = identifier.type
+            metadata['identifier_date_last' + '_' + str(i)] = unicode(identifier.date_last)
+            metadata['identifier_notes' + '_' + str(i)] = identifier.notes
+            i += 1
+
+        analyzers = ['channels', 'samplerate', 'duration', 'resolution', 'mime_type']
+        for analyzer_id in analyzers:
+            analysis = MediaItemAnalysis.objects.filter(item=self, analyzer_id=analyzer_id)
+            if analysis:
+                metadata[analyzer_id] = analysis[0].value
+
+        metadata['file_size'] = unicode(self.size())
+        metadata['thumbnail'] = get_full_url(reverse('telemeta-item-visualize',
+                                            kwargs={'public_id': self.public_id,
+                                                    'grapher_id': 'waveform_centroid',
+                                                    'width': 346,
+                                                    'height': 130}))
+
+        # One ID only
+        identifiers = self.identifiers.all()
+        if identifiers:
+            identifier = identifiers[0]
+            metadata['identifier_id'] = identifier.identifier
+            metadata['identifier_type'] = identifier.type
+            metadata['identifier_date'] = unicode(identifier.date_last)
+            metadata['identifier_note'] = identifier.notes
+
+        return metadata
+
+
+class MediaItemRelated(MediaRelated):
+    "Item related media"
+
+    item = ForeignKey('MediaItem', related_name="related", verbose_name=_('item'))
+
+    def save(self, force_insert=False, force_update=False, using=False):
+        super(MediaItemRelated, self).save(force_insert, force_update)
+
+    def parse_markers(self, **kwargs):
+        # Parse KDEnLive session
+        if self.file:
+            if self.is_kdenlive_session():
+                session = KDEnLiveSession(self.file.path)
+                markers = session.markers(**kwargs)
+                for marker in markers:
+                    m = MediaItemMarker(item=self.item)
+                    m.public_id = get_random_hash()
+                    m.time = float(marker['time'])
+                    m.title = marker['comment']
+                    m.save()
+                return markers
+
+    class Meta(MetaCore):
+        db_table = 'media_item_related'
+        verbose_name = _('item related media')
+        verbose_name_plural = _('item related media')
+
+
+class MediaItemKeyword(ModelCore):
+    "Item keyword"
+    item    = ForeignKey('MediaItem', verbose_name=_('item'), related_name="keyword_relations")
+    keyword = ForeignKey('ContextKeyword', verbose_name=_('keyword'), related_name="item_relations")
+
+    class Meta(MetaCore):
+        db_table = 'media_item_keywords'
+        unique_together = (('item', 'keyword'),)
+
+
+class MediaItemPerformance(ModelCore):
+    "Item performance"
+    media_item      = ForeignKey('MediaItem', related_name="performances", verbose_name=_('item'))
+    instrument      = WeakForeignKey('Instrument', related_name="performances", verbose_name=_('composition'))
+    alias           = WeakForeignKey('InstrumentAlias', related_name="performances", verbose_name=_('vernacular name'))
+    instruments_num = CharField(_('number'))
+    musicians       = CharField(_('interprets'))
+
+    class Meta(MetaCore):
+        db_table = 'media_item_performances'
+
+
+class MediaItemAnalysis(ModelCore):
+    "Item analysis result computed by TimeSide"
+
+    element_type = 'analysis'
+    item  = ForeignKey('MediaItem', related_name="analysis", verbose_name=_('item'))
+    analyzer_id = CharField(_('id'), required=True)
+    name = CharField(_('name'))
+    value = CharField(_('value'))
+    unit = CharField(_('unit'))
+
+    class Meta(MetaCore):
+        db_table = 'media_analysis'
+        ordering = ['name']
+
+    def to_dict(self):
+        if self.analyzer_id == 'duration':
+            if '.' in self.value:
+                value = self.value.split('.')
+                self.value = '.'.join([value[0], value[1][:2]])
+        return {'id': self.analyzer_id, 'name': self.name, 'value': self.value, 'unit': self.unit}
+
+
+
+class MediaItemMarker(MediaResource):
+    "2D marker object : text value vs. time (in seconds)"
+
+    element_type = 'marker'
+
+    item            = ForeignKey('MediaItem', related_name="markers", verbose_name=_('item'))
+    public_id       = CharField(_('public_id'), required=True)
+    time            = FloatField(_('time (s)'))
+    title           = CharField(_('title'))
+    date            = DateTimeField(_('date'), auto_now=True)
+    description     = TextField(_('description'))
+    author          = ForeignKey(User, related_name="markers", verbose_name=_('author'),
+                                 blank=True, null=True)
+
+    class Meta(MetaCore):
+        db_table = 'media_markers'
+        ordering = ['time']
+
+    def __unicode__(self):
+        if self.title:
+            return self.title
+        else:
+            return self.public_id
+
+
+class MediaItemTranscoded(MediaResource):
+    "Item file transcoded"
+
+    element_type = 'transcoded item'
+
+    item            = models.ForeignKey('MediaItem', related_name="transcoded", verbose_name=_('item'))
+    mimetype        = models.CharField(_('mime_type'), max_length=255, blank=True)
+    date_added      = DateTimeField(_('date'), auto_now_add=True)
+    status          = models.IntegerField(_('status'), choices=ITEM_TRANSODING_STATUS, default=1)
+    file            = models.FileField(_('file'), upload_to='items/%Y/%m/%d', max_length=1024, blank=True)
+
+    @property
+    def mime_type(self):
+        if not self.mimetype:
+            if self.file:
+                if os.path.exists(self.file.path):
+                    self.mimetype = mimetypes.guess_type(self.file.path)[0]
+                    self.save()
+                    return self.mimetype
+                else:
+                    return 'none'
+            else:
+                return 'none'
+        else:
+            return self.mimetype
+
+    def __unicode__(self):
+        if self.item.title:
+            return self.item.title + ' - ' + self.mime_type
+        else:
+            return self.item.public_id + ' - ' + self.mime_type
+
+    class Meta(MetaCore):
+        db_table = app_name + '_media_transcoded'
+
+
+class MediaItemTranscodingFlag(ModelCore):
+    "Item flag to know if the MediaItem has been transcoded to a given format"
+
+    item            = ForeignKey('MediaItem', related_name="transcoding", verbose_name=_('item'))
+    mime_type       = CharField(_('mime_type'), required=True)
+    date            = DateTimeField(_('date'), auto_now=True)
+    value           = BooleanField(_('transcoded'))
+
+    class Meta(MetaCore):
+        db_table = 'media_transcoding'
+
+
+class MediaItemIdentifier(Identifier):
+    """Item identifier"""
+
+    item = ForeignKey(MediaItem, related_name="identifiers", verbose_name=_('item'))
+
+    class Meta(MetaCore):
+        db_table = 'media_item_identifier'
+        verbose_name = _('item identifier')
+        verbose_name_plural = _('item identifiers')
+        unique_together = ('identifier', 'item')
+
+
+
+class MediaPart(MediaResource):
+    "Describe an item part"
+    element_type = 'part'
+    item  = ForeignKey('MediaItem', related_name="parts", verbose_name=_('item'))
+    title = CharField(_('title'), required=True)
+    start = FloatField(_('start'), required=True)
+    end   = FloatField(_('end'), required=True)
+
+    class Meta(MetaCore):
+        db_table = 'media_parts'
+        verbose_name = _('item part')
+
+    def __unicode__(self):
+        return self.title
diff --git a/telemeta/models/media.py b/telemeta/models/media.py
deleted file mode 100644 (file)
index 32bcb9a..0000000
+++ /dev/null
@@ -1,1069 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (C) 2010 Samalyse SARL
-# Copyright (C) 2010-2014 Parisson SARL
-
-# This software is a computer program whose purpose is to backup, analyse,
-# transcode and stream any audio content with its metadata over a web frontend.
-
-# This software is governed by the CeCILL  license under French law and
-# abiding by the rules of distribution of free software.  You can  use,
-# modify and/ or redistribute the software under the terms of the CeCILL
-# license as circulated by CEA, CNRS and INRIA at the following URL
-# "http://www.cecill.info".
-
-# As a counterpart to the access to the source code and  rights to copy,
-# modify and redistribute granted by the license, users are provided only
-# with a limited warranty  and the software's author,  the holder of the
-# economic rights,  and the successive licensors  have only  limited
-# liability.
-
-# In this respect, the user's attention is drawn to the risks associated
-# with loading,  using,  modifying and/or developing or reproducing the
-# software by the user in light of its specific status of free software,
-# that may mean  that it is complicated to manipulate,  and  that  also
-# therefore means  that it is reserved for developers  and  experienced
-# professionals having in-depth computer knowledge. Users are therefore
-# encouraged to load and test the software's suitability as regards their
-# requirements in conditions enabling the security of their systems and/or
-# data to be ensured and,  more generally, to use and operate it in the
-# same conditions as regards security.
-
-# The fact that you are presently reading this means that you have had
-# knowledge of the CeCILL license and that you accept its terms.
-#
-# Authors: Olivier Guilyardi <olivier@samalyse.com>
-#          David LIPSZYC <davidlipszyc@gmail.com>
-#          Guillaume Pellerin <yomguy@parisson.com>
-
-from __future__ import division
-import re, os, random
-import mimetypes
-from django.contrib.auth.models import User
-from django.utils.translation import ugettext_lazy as _
-from django.utils.translation import ugettext
-from django.core.exceptions import ValidationError
-from telemeta.models.core import *
-from telemeta.models.enum import ContextKeyword
-from telemeta.util.unaccent import unaccent_icmp
-from telemeta.models.location import LocationRelation, Location
-from telemeta.models.system import Revision
-from telemeta.models.query import *
-from telemeta.models.instrument import *
-from telemeta.models.enum import *
-from telemeta.models.language import *
-from telemeta.models.format import *
-from telemeta.util.kdenlive.session import *
-from django.db import models
-from django.db.models import URLField
-from django.conf import settings
-from django.core.urlresolvers import reverse, reverse_lazy
-from django.contrib.sites.models import Site
-from django.forms.models import model_to_dict
-
-
-strict_code = getattr(settings, 'TELEMETA_STRICT_CODE', False)
-
-# Special code regex of collections for the branch
-collection_published_code_regex = getattr(settings, 'COLLECTION_PUBLISHED_CODE_REGEX', '[A-Za-z0-9._-]*')
-collection_unpublished_code_regex = getattr(settings, 'COLLECTION_UNPUBLISHED_CODE_REGEX', '[A-Za-z0-9._-]*')
-
-# CREM
-#collection_published_code_regex   = 'CNRSMH_E_[0-9]{4}(?:_[0-9]{3}){2}'
-#collection_unpublished_code_regex = 'CNRSMH_I_[0-9]{4}_[0-9]{3}'
-
-collection_code_regex = '(?:%s|%s)' % (collection_published_code_regex,
-                                       collection_unpublished_code_regex)
-
-item_published_code_regex = getattr(settings, 'ITEM_PUBLISHED_CODE_REGEX', '[A-Za-z0-9._-]*')
-item_unpublished_code_regex = getattr(settings, 'ITEM_UNPUBLISHED_CODE_REGEX', '[A-Za-z0-9._-]*')
-
-# CREM
-# item_published_code_regex    = collection_published_code_regex + '(?:_[0-9]{2,3}){1,2}'
-# item_unpublished_code_regex  = collection_unpublished_code_regex + '_[0-9]{2,3}(?:_[0-9]{2,3}){0,2}'
-
-item_code_regex = '(?:%s|%s)' % (item_published_code_regex, item_unpublished_code_regex)
-
-PUBLIC_ACCESS_CHOICES = (('none', _('none')), ('metadata', _('metadata')),
-                         ('mixed', _('mixed')), ('full', _('full')))
-
-ITEM_PUBLIC_ACCESS_CHOICES = (('none', _('none')), ('metadata', _('metadata')),
-                         ('full', _('full')))
-
-ITEM_TRANSODING_STATUS = ((0, _('broken')), (1, _('pending')), (2, _('processing')),
-                         (3, _('done')), (5, _('ready')))
-
-mimetypes.add_type('video/webm','.webm')
-
-app_name = 'telemeta'
-
-
-def get_random_hash():
-    hash = random.getrandbits(64)
-    return "%016x" % hash
-
-def get_full_url(path):
-    return 'http://' + Site.objects.get_current().domain + path
-
-
-class MediaResource(ModelCore):
-    "Base class of all media objects"
-
-    def public_access_label(self):
-        if self.public_access == 'metadata':
-            return _('Metadata only')
-        elif self.public_access == 'full':
-            return _('Sound and metadata')
-
-        return _('Private data')
-    public_access_label.verbose_name = _('access type')
-
-    def set_revision(self, user):
-        "Save a media object and add a revision"
-        Revision.touch(self, user)
-
-    def get_revision(self):
-        return Revision.objects.filter(element_type=self.element_type, element_id=self.id).order_by('-time')[0]
-
-    class Meta:
-        abstract = True
-
-
-class MediaBaseResource(MediaResource):
-    "Describe a media base resource"
-
-    title                 = CharField(_('title'), required=True)
-    description           = CharField(_('description_old'))
-    descriptions          = TextField(_('description'))
-    code                  = CharField(_('code'), unique=True, required=True)
-    public_access         = CharField(_('public access'), choices=PUBLIC_ACCESS_CHOICES, max_length=16, default="metadata")
-
-    def __unicode__(self):
-        return self.code
-
-    @property
-    def public_id(self):
-        return self.code
-
-    def save(self, force_insert=False, force_update=False, user=None, code=None):
-        super(MediaBaseResource, self).save(force_insert, force_update)
-
-    def get_fields(self):
-        return self._meta.fields
-
-    class Meta(MetaCore):
-        abstract = True
-
-
-class MediaRelated(MediaResource):
-    "Related media"
-
-    element_type = 'media'
-
-    title           = CharField(_('title'))
-    date            = DateTimeField(_('date'), auto_now=True)
-    description     = TextField(_('description'))
-    mime_type       = CharField(_('mime_type'))
-    url             = CharField(_('url'), max_length=500)
-    credits         = CharField(_('credits'))
-    file            = FileField(_('file'), upload_to='items/%Y/%m/%d', db_column="filename", max_length=255)
-
-    def is_image(self):
-        is_url_image = False
-        if self.url:
-            url_types = ['.png', '.jpg', '.gif', '.jpeg']
-            for type in url_types:
-                if type in self.url or type.upper() in self.url:
-                    is_url_image = True
-        return 'image' in self.mime_type or is_url_image
-
-    def save(self, force_insert=False, force_update=False, author=None):
-        super(MediaRelated, self).save(force_insert, force_update)
-
-    def set_mime_type(self):
-        if self.file:
-            self.mime_type = mimetypes.guess_type(self.file.path)[0]
-
-    def is_kdenlive_session(self):
-        if self.file:
-            return '.kdenlive' in self.file.path
-        else:
-            return False
-
-    def __unicode__(self):
-        if self.title and not re.match('^ *N *$', self.title):
-            title = self.title
-        else:
-            title = unicode(self.item)
-        return title
-
-    class Meta:
-        abstract = True
-
-
-class MediaCollection(MediaResource):
-    "Describe a collection of items"
-
-    element_type = 'collection'
-
-    def is_valid_collection_code(value):
-        "Check if the collection code is well formed"
-        regex = '^' + collection_code_regex + '$'
-        if not re.match(regex, value):
-            raise ValidationError(u'%s is not a valid collection code' % value)
-
-    # General informations
-    title                 = CharField(_('title'), required=True)
-    alt_title             = CharField(_('original title / translation'))
-    creator               = CharField(_('depositor / contributor'), help_text=_('First name, Last name ; First name, Last name'))
-    description           = TextField(_('description'))
-    recording_context     = WeakForeignKey('RecordingContext', related_name="collections", verbose_name=_('recording context'))
-    recorded_from_year    = IntegerField(_('recording year (from)'), help_text=_('YYYY'))
-    recorded_to_year      = IntegerField(_('recording year (until)'), help_text=_('YYYY'))
-    year_published        = IntegerField(_('year published'), help_text=_('YYYY'))
-    public_access         = CharField(_('access type'), choices=PUBLIC_ACCESS_CHOICES, max_length=16, default="metadata")
-
-    # Geographic and cultural informations
-    # See "countries" and "ethnic_groups" methods below
-
-    # Legal notices
-    collector             = CharField(_('recordist'), help_text=_('First name, Last name ; First name, Last name'))
-    publisher             = WeakForeignKey('Publisher', related_name="collections", verbose_name=_('publisher'))
-    publisher_collection  = WeakForeignKey('PublisherCollection', related_name="collections", verbose_name=_('publisher collection'))
-    publisher_serial      = CharField(_('publisher serial number'))
-    booklet_author        = CharField(_('booklet author'), blank=True)
-    reference             = CharField(_('publisher reference'))
-    external_references   = TextField(_('bibliographic references'))
-
-    auto_period_access    = BooleanField(_('automatic access after a rolling period'), default=True)
-    legal_rights          = WeakForeignKey('LegalRight', related_name="collections", verbose_name=_('legal rights'))
-
-    # Archiving data
-    code                  = CharField(_('code'), unique=True, required=True, validators=[is_valid_collection_code])
-    old_code              = CharField(_('old code'), unique=False, null=True, blank=True)
-    acquisition_mode      = WeakForeignKey('AcquisitionMode', related_name="collections", verbose_name=_('mode of acquisition'))
-    cnrs_contributor      = CharField(_('CNRS depositor'))
-    copy_type             = WeakForeignKey('CopyType', related_name="collections", verbose_name=_('copy type'))
-    metadata_author       = WeakForeignKey('MetadataAuthor', related_name="collections", verbose_name=_('record author'))
-    booklet_description   = TextField(_('related documentation'))
-    publishing_status     = WeakForeignKey('PublishingStatus', related_name="collections", verbose_name=_('secondary edition'))
-    status                = WeakForeignKey('Status', related_name="collections", verbose_name=_('collection status'))
-    alt_copies            = TextField(_('copies'))
-    comment               = TextField(_('comment'))
-    metadata_writer       = WeakForeignKey('MetadataWriter', related_name="collections", verbose_name=_('record writer'))
-    archiver_notes        = TextField(_('archiver notes'))
-    items_done            = CharField(_('items finished'))
-    collector_is_creator  = BooleanField(_('recordist identical to depositor'))
-    is_published          = BooleanField(_('published'))
-    conservation_site     = CharField(_('conservation site'))
-
-    # Technical data
-    media_type            = WeakForeignKey('MediaType', related_name="collections", verbose_name=_('media type'))
-    approx_duration       = DurationField(_('estimated duration'), help_text='hh:mm:ss')
-    physical_items_num    = IntegerField(_('number of components (medium / piece)'))
-    original_format       = WeakForeignKey('OriginalFormat', related_name="collections", verbose_name=_('original format'))
-    physical_format       = WeakForeignKey('PhysicalFormat', related_name="collections", verbose_name=_('archive format'))
-    ad_conversion         = WeakForeignKey('AdConversion', related_name='collections', verbose_name=_('digitization'))
-
-    # No more used old fields
-    alt_ids               = CharField(_('copies (obsolete field)'))
-    travail               = CharField(_('archiver notes (obsolete field)'))
-
-    # All
-    objects               = MediaCollectionManager()
-
-    exclude = ['alt_ids', 'travail']
-
-    class Meta(MetaCore):
-        db_table = 'media_collections'
-        ordering = ['code']
-        verbose_name = _('collection')
-
-    def __unicode__(self):
-        return self.code
-
-    def save(self, force_insert=False, force_update=False, user=None, code=None):
-        super(MediaCollection, self).save(force_insert, force_update)
-
-    @property
-    def public_id(self):
-        return self.code
-
-    @property
-    def has_mediafile(self):
-        "Tell wether this collection has any media files attached to its items"
-        items = self.items.all()
-        for item in items:
-            if item.file:
-                return True
-        return False
-
-    def __name_cmp(self, obj1, obj2):
-        return unaccent_icmp(obj1.name, obj2.name)
-
-    def countries(self):
-        "Return the countries of the items"
-        countries = []
-        for item in self.items.filter(location__isnull=False):
-            for country in item.location.countries():
-                if not country in countries:
-                    countries.append(country)
-        countries.sort(self.__name_cmp)
-        return countries
-    countries.verbose_name = _("states / nations")
-
-    def main_countries(self):
-        "Return the main countries of the items (no aliases or ancestors)"
-        countries = []
-        for item in self.items.filter(location__isnull=False):
-            if not item.location in countries:
-                countries.append(item.location)
-        countries.sort(self.__name_cmp)
-        return countries
-    main_countries.verbose_name = _("states / nations")
-
-    def ethnic_groups(self):
-        "Return the ethnic groups of the items"
-        groups = []
-        items = self.items.all()
-        for item in items:
-            if item.ethnic_group and not item.ethnic_group in groups:
-                groups.append(item.ethnic_group)
-
-        cmp = lambda a, b: unaccent_icmp(a.value, b.value)
-        groups.sort(cmp)
-
-        return groups
-    ethnic_groups.verbose_name = _('populations / social groups')
-
-    def computed_duration(self):
-        duration = Duration()
-        for item in self.items.all():
-            duration += item.computed_duration()
-        return duration
-    computed_duration.verbose_name = _('computed duration')
-
-    def computed_size(self):
-        "Return the total size of a collection"
-        size = 0
-        for item in self.items.all():
-            size += item.size()
-        return size
-    computed_size.verbose_name = _('collection size')
-
-    def document_status(self):
-        if '_I_' in self.public_id:
-            return ugettext('Unpublished')
-        elif '_E_' in self.public_id:
-            return ugettext('Published')
-        else:
-            return ''
-
-    def get_url(self):
-        return get_full_url(reverse('telemeta-collection-detail', kwargs={'public_id':self.pk}))
-
-    def to_dict_with_more(self):
-        # metadata = model_to_dict(self, fields=[], exclude=self.exclude)
-        metadata = self.to_dict()
-        for key in self.exclude:
-            if key in metadata.keys():
-                del metadata[key]
-
-        metadata['url'] = get_full_url(reverse('telemeta-collection-detail', kwargs={'public_id':self.pk}))
-        metadata['doc_status'] = self.document_status()
-        metadata['countries'] = ';'.join([location.name for location in self.main_countries()])
-        metadata['ethnic_groups'] = ';'.join([group.value for group in self.ethnic_groups()])
-        metadata['last_modification_date'] = unicode(self.get_revision().time)
-        metadata['computed_duration'] = unicode(self.computed_duration())
-        metadata['computed_size'] = unicode(self.computed_size())
-        metadata['number_of_items'] = unicode(self.items.all().count())
-
-        i = 0
-        for media in self.related.all():
-            metadata['related_media_title' + '_' + str(i)] = media.title
-            if media.url:
-                tag = 'related_media_url' + '_' + str(i)
-                metadata[tag] = media.url
-            elif media.url:
-                metadata[tag] = get_full_url(reverse('telemeta-collection-related',
-                                            kwargs={'public_id': self.public_id, 'media_id': media.id}))
-            i += 1
-
-        # One ID only
-        identifiers = self.identifiers.all()
-        if identifiers:
-            identifier = identifiers[0]
-            metadata['identifier_id'] = identifier.identifier
-            metadata['identifier_type'] = identifier.type
-            metadata['identifier_date'] = unicode(identifier.date_last)
-            metadata['identifier_note'] = identifier.notes
-
-        # All IDs
-        # i = 0
-        # for indentifier in self.identifiers.all():
-        #     metadata['identifier' + '_' + str(i)] = identifier.identifier
-        #     metadata['identifier_type' + '_' + str(i)] = identifier.type
-        #     metadata['identifier_date_last' + '_' + str(i)] = unicode(identifier.date_last)
-        #     metadata['identifier_notes' + '_' + str(i)] = identifier.notes
-        #     i += 1
-
-        return metadata
-
-
-class MediaCollectionRelated(MediaRelated):
-    "Collection related media"
-
-    collection      = ForeignKey('MediaCollection', related_name="related", verbose_name=_('collection'))
-
-    class Meta(MetaCore):
-        db_table = 'media_collection_related'
-        verbose_name = _('collection related media')
-        verbose_name_plural = _('collection related media')
-
-
-class MediaItem(MediaResource):
-    "Describe an item"
-
-    element_type = 'item'
-
-    # Main Informations
-    title                 = CharField(_('title'))
-    alt_title             = CharField(_('original title / translation'))
-    collector             = CharField(_('collector'), help_text=_('First name, Last name ; First name, Last name'))
-    collection            = ForeignKey('MediaCollection', related_name="items", verbose_name=_('collection'))
-    recorded_from_date    = DateField(_('recording date (from)'), help_text=_('YYYY-MM-DD'))
-    recorded_to_date      = DateField(_('recording date (until)'), help_text=_('YYYY-MM-DD'))
-    public_access         = CharField(_('access type'), choices=ITEM_PUBLIC_ACCESS_CHOICES, max_length=16, default="metadata")
-
-    # Geographic and cultural informations
-    location              = WeakForeignKey('Location', verbose_name=_('location'))
-    location_comment      = CharField(_('location details'))
-    cultural_area         = CharField(_('cultural area'))
-    language              = CharField(_('language'))
-    language_iso          = ForeignKey('Language', related_name="items", verbose_name=_('Language (ISO norm)'), blank=True, null=True, on_delete=models.SET_NULL)
-    ethnic_group          = WeakForeignKey('EthnicGroup', related_name="items", verbose_name=_('population / social group'))
-    context_comment       = TextField(_('Ethnographic context'))
-
-    # Musical informations
-    moda_execut           = CharField(_('implementing rules'))
-    vernacular_style      = WeakForeignKey('VernacularStyle', related_name="items", verbose_name=_('vernacular style'))
-    generic_style         = WeakForeignKey('GenericStyle', related_name="items", verbose_name=_('generic style'))
-    author                = CharField(_('author / compositor'), help_text=_('First name, Last name ; First name, Last name'))
-
-    # Legal mentions
-    organization          = WeakForeignKey('Organization', verbose_name=_('organization'))
-    depositor             = CharField(_('depositor'))
-    rights                = WeakForeignKey('Rights', verbose_name=_('rights'))
-
-    # Archiving data
-    code                  = CharField(_('code'), unique=True, blank=True, required=True, help_text=_('CollectionCode_ItemCode'))
-    old_code              = CharField(_('original code'), unique=False, blank=True)
-    track                 = CharField(_('item number'))
-    collector_selection   = CharField(_('collector selection'))
-    collector_from_collection = BooleanField(_('collector as in collection'))
-    creator_reference     = CharField(_('creator reference'))
-    external_references   = TextField(_('published references'))
-    auto_period_access    = BooleanField(_('automatic access after a rolling period'), default=True)
-    comment               = TextField(_('remarks'))
-
-    # Technical data
-    media_type            = WeakForeignKey('MediaType', related_name="items", verbose_name=_('media type'))
-    approx_duration       = DurationField(_('approximative duration'), blank=True, help_text=_('hh:mm:ss'))
-    mimetype              = CharField(_('mime type'), max_length=255, blank=True)
-    file                  = FileField(_('file'), upload_to='items/%Y/%m/%d', db_column="filename", max_length=1024)
-    url                   = URLField(_('URL'), max_length=512, blank=True)
-
-    # LAM
-    recordist             = CharField(_('recordist'))
-    digitalist            = CharField(_('digitalist'))
-    digitization_date     = DateField(_('digitization date'))
-    publishing_date       = DateField(_('publishing date'))
-    scientist             = CharField(_('scientist'), help_text=_('First name, Last name ; First name, Last name'))
-    topic                 = WeakForeignKey('Topic', verbose_name=_('topic'))
-    summary               = TextField(_('summary'))
-    contributor           = CharField(_('contributor'))
-
-    # Manager
-    objects               = MediaItemManager()
-
-    exclude = ['copied_from_item', 'mimetype', 'url',
-                    'organization', 'depositor', 'rights',
-                    'recordist', 'digitalist', 'digitization_date',
-                    'publishing_date', 'scientist', 'topic',
-                    'summary', 'contributor', ]
-
-    def keywords(self):
-        return ContextKeyword.objects.filter(item_relations__item = self)
-    keywords.verbose_name = _('keywords')
-
-    @property
-    def public_id(self):
-        if self.code:
-            return self.code
-        return str(self.id)
-
-    @property
-    def mime_type(self):
-        if not self.mimetype:
-            if self.file:
-                if os.path.exists(self.file.path):
-                    self.mimetype = mimetypes.guess_type(self.file.path)[0]
-                    self.save()
-                    return self.mimetype
-                else:
-                    return 'none'
-            else:
-                return 'none'
-        else:
-            return _('none')
-
-
-    class Meta(MetaCore):
-        db_table = 'media_items'
-        permissions = (("can_play_all_items", "Can play all media items"),
-                       ("can_download_all_items", "Can download all media items"), )
-        verbose_name = _('item')
-
-    def is_valid_code(self, code):
-        "Check if the item code is well formed"
-        if not re.match('^' + self.collection.code, self.code):
-            return False
-        if self.collection.is_published:
-            regex = '^' + item_published_code_regex + '$'
-        else:
-            regex = '^' + item_unpublished_code_regex + '$'
-        if re.match(regex, code):
-            return True
-        return False
-
-    def clean(self):
-        if strict_code:
-            if self.code and not self.is_valid_code(self.code):
-                raise ValidationError("%s is not a valid item code for collection %s"
-                                            % (self.code, self.collection.code))
-
-    def save(self, force_insert=False, force_update=False):
-        super(MediaItem, self).save(force_insert, force_update)
-
-    def computed_duration(self):
-        "Tell the length in seconds of this item media data"
-        return self.approx_duration
-
-    computed_duration.verbose_name = _('computed duration')
-
-    def __unicode__(self):
-        if self.title and not re.match('^ *N *$', self.title):
-            title = self.title
-        else:
-            title = unicode(self.collection.title)
-        if self.track:
-            title += ' ' + self.track
-        return title
-
-    def get_source(self):
-        source = None
-        if self.file and os.path.exists(self.file.path):
-            source = self.file.path
-        elif self.url:
-            source = self.url
-        return source
-
-    @property
-    def instruments(self):
-        "Return the instruments of the item"
-        instruments = []
-        performances = MediaItemPerformance.objects.filter(media_item=self)
-        for performance in performances:
-            instrument = performance.instrument
-            alias = performance.alias
-            if not instrument in instruments:
-                instruments.append(instrument)
-            if not alias in instruments:
-                instruments.append(alias)
-
-        instruments.sort(self.__name_cmp)
-        return instruments
-
-        instruments.verbose_name = _("instruments")
-
-    def size(self):
-        if self.file and os.path.exists(self.file.path):
-            return self.file.size
-        else:
-            return 0
-    size.verbose_name = _('item size')
-
-    def get_url(self):
-        return get_full_url(reverse('telemeta-item-detail', kwargs={'public_id':self.pk}))
-
-    def to_dict_with_more(self):
-        # metadata = model_to_dict(self, fields=[], exclude=self.exclude)
-        metadata = self.to_dict()
-        for key in self.exclude:
-            if key in metadata.keys():
-                del metadata[key]
-
-        metadata['url'] = self.get_url()
-        metadata['last_modification_date'] = unicode(self.get_revision().time)
-        metadata['collection'] = self.collection.get_url()
-
-        keywords = []
-        for keyword in self.keywords():
-            keywords.append(keyword.value)
-        metadata['keywords'] = ';'.join(keywords)
-
-        i = 0
-        for media in self.related.all():
-            if media.title:
-                tag = 'related_media_title' + '_' + str(i)
-                metadata[tag] = media.title
-            else:
-                metadata[tag] = ''
-            if media.url:
-                tag = 'related_media_url' + '_' + str(i)
-                metadata[tag] = media.url
-            elif media.url:
-                metadata[tag] = get_full_url(reverse('telemeta-collection-related',
-                                            kwargs={'public_id': self.public_id, 'media_id': media.id}))
-            i += 1
-
-
-        instruments = []
-        instrument_vernacular_names = []
-        performers = []
-
-        for performance in self.performances.all():
-            if performance.instrument:
-                instruments.append(performance.instrument.name)
-            if performance.alias:
-                instrument_vernacular_names.append(performance.alias.name)
-            if performance.musicians:
-                performers.append(performance.musicians.replace(' et ', ';'))
-
-        metadata['instruments'] = ';'.join(instruments)
-        metadata['instrument_vernacular_names'] = ';'.join(instrument_vernacular_names)
-        metadata['performers'] = ';'.join(performers)
-
-        i = 0
-        for indentifier in self.identifiers.all():
-            metadata['identifier' + '_' + str(i)] = identifier.identifier
-            metadata['identifier_type' + '_' + str(i)] = identifier.type
-            metadata['identifier_date_last' + '_' + str(i)] = unicode(identifier.date_last)
-            metadata['identifier_notes' + '_' + str(i)] = identifier.notes
-            i += 1
-
-        analyzers = ['channels', 'samplerate', 'duration', 'resolution', 'mime_type']
-        for analyzer_id in analyzers:
-            analysis = MediaItemAnalysis.objects.filter(item=self, analyzer_id=analyzer_id)
-            if analysis:
-                metadata[analyzer_id] = analysis[0].value
-
-        metadata['file_size'] = unicode(self.size())
-        metadata['thumbnail'] = get_full_url(reverse('telemeta-item-visualize',
-                                            kwargs={'public_id': self.public_id,
-                                                    'grapher_id': 'waveform_centroid',
-                                                    'width': 346,
-                                                    'height': 130}))
-
-        # One ID only
-        identifiers = self.identifiers.all()
-        if identifiers:
-            identifier = identifiers[0]
-            metadata['identifier_id'] = identifier.identifier
-            metadata['identifier_type'] = identifier.type
-            metadata['identifier_date'] = unicode(identifier.date_last)
-            metadata['identifier_note'] = identifier.notes
-
-        return metadata
-
-
-class MediaItemRelated(MediaRelated):
-    "Item related media"
-
-    item            = ForeignKey('MediaItem', related_name="related", verbose_name=_('item'))
-
-    def save(self, force_insert=False, force_update=False, using=False):
-        super(MediaItemRelated, self).save(force_insert, force_update)
-
-    def parse_markers(self, **kwargs):
-        # Parse KDEnLive session
-        if self.file:
-            if self.is_kdenlive_session():
-                session = KDEnLiveSession(self.file.path)
-                markers = session.markers(**kwargs)
-                for marker in markers:
-                    m = MediaItemMarker(item=self.item)
-                    m.public_id = get_random_hash()
-                    m.time = float(marker['time'])
-                    m.title = marker['comment']
-                    m.save()
-                return markers
-
-    class Meta(MetaCore):
-        db_table = 'media_item_related'
-        verbose_name = _('item related media')
-        verbose_name_plural = _('item related media')
-
-
-class MediaItemKeyword(ModelCore):
-    "Item keyword"
-    item    = ForeignKey('MediaItem', verbose_name=_('item'), related_name="keyword_relations")
-    keyword = ForeignKey('ContextKeyword', verbose_name=_('keyword'), related_name="item_relations")
-
-    class Meta(MetaCore):
-        db_table = 'media_item_keywords'
-        unique_together = (('item', 'keyword'),)
-
-
-class MediaItemPerformance(ModelCore):
-    "Item performance"
-    media_item      = ForeignKey('MediaItem', related_name="performances", verbose_name=_('item'))
-    instrument      = WeakForeignKey('Instrument', related_name="performances", verbose_name=_('composition'))
-    alias           = WeakForeignKey('InstrumentAlias', related_name="performances", verbose_name=_('vernacular name'))
-    instruments_num = CharField(_('number'))
-    musicians       = CharField(_('interprets'))
-
-    class Meta(MetaCore):
-        db_table = 'media_item_performances'
-
-
-class MediaItemAnalysis(ModelCore):
-    "Item analysis result computed by TimeSide"
-
-    element_type = 'analysis'
-    item  = ForeignKey('MediaItem', related_name="analysis", verbose_name=_('item'))
-    analyzer_id = CharField(_('id'), required=True)
-    name = CharField(_('name'))
-    value = CharField(_('value'))
-    unit = CharField(_('unit'))
-
-    class Meta(MetaCore):
-        db_table = 'media_analysis'
-        ordering = ['name']
-
-    def to_dict(self):
-        if self.analyzer_id == 'duration':
-            if '.' in self.value:
-                value = self.value.split('.')
-                self.value = '.'.join([value[0], value[1][:2]])
-        return {'id': self.analyzer_id, 'name': self.name, 'value': self.value, 'unit': self.unit}
-
-
-class MediaPart(MediaResource):
-    "Describe an item part"
-    element_type = 'part'
-    item  = ForeignKey('MediaItem', related_name="parts", verbose_name=_('item'))
-    title = CharField(_('title'), required=True)
-    start = FloatField(_('start'), required=True)
-    end   = FloatField(_('end'), required=True)
-
-    class Meta(MetaCore):
-        db_table = 'media_parts'
-        verbose_name = _('item part')
-
-    def __unicode__(self):
-        return self.title
-
-class Playlist(ModelCore):
-    "Item, collection or marker playlist"
-    element_type = 'playlist'
-    public_id      = CharField(_('public_id'), required=True)
-    author         = ForeignKey(User, related_name="playlists", db_column="author")
-    title          = CharField(_('title'), required=True)
-    description    = TextField(_('description'))
-
-    class Meta(MetaCore):
-        db_table = 'playlists'
-
-    def __unicode__(self):
-        return self.title
-
-
-class PlaylistResource(ModelCore):
-    "Playlist components"
-    RESOURCE_TYPE_CHOICES = (('item', 'item'), ('collection', 'collection'),
-                             ('marker', 'marker'), ('fonds', 'fonds'), ('corpus', 'corpus'))
-    element_type       = 'playlist_resource'
-    public_id          = CharField(_('public_id'), required=True)
-    playlist           = ForeignKey('Playlist', related_name="resources", verbose_name=_('playlist'))
-    resource_type      = CharField(_('resource_type'), choices=RESOURCE_TYPE_CHOICES, required=True)
-    resource_id        = CharField(_('resource_id'), required=True)
-
-    class Meta(MetaCore):
-        db_table = 'playlist_resources'
-
-
-class MediaItemMarker(MediaResource):
-    "2D marker object : text value vs. time (in seconds)"
-
-    element_type = 'marker'
-
-    item            = ForeignKey('MediaItem', related_name="markers", verbose_name=_('item'))
-    public_id       = CharField(_('public_id'), required=True)
-    time            = FloatField(_('time (s)'))
-    title           = CharField(_('title'))
-    date            = DateTimeField(_('date'), auto_now=True)
-    description     = TextField(_('description'))
-    author          = ForeignKey(User, related_name="markers", verbose_name=_('author'),
-                                 blank=True, null=True)
-
-    class Meta(MetaCore):
-        db_table = 'media_markers'
-        ordering = ['time']
-
-    def __unicode__(self):
-        if self.title:
-            return self.title
-        else:
-            return self.public_id
-
-
-class MediaItemTranscoded(MediaResource):
-    "Item file transcoded"
-
-    element_type = 'transcoded item'
-
-    item            = models.ForeignKey('MediaItem', related_name="transcoded", verbose_name=_('item'))
-    mimetype        = models.CharField(_('mime_type'), max_length=255, blank=True)
-    date_added      = DateTimeField(_('date'), auto_now_add=True)
-    status          = models.IntegerField(_('status'), choices=ITEM_TRANSODING_STATUS, default=1)
-    file            = models.FileField(_('file'), upload_to='items/%Y/%m/%d', max_length=1024, blank=True)
-
-    @property
-    def mime_type(self):
-        if not self.mimetype:
-            if self.file:
-                if os.path.exists(self.file.path):
-                    self.mimetype = mimetypes.guess_type(self.file.path)[0]
-                    self.save()
-                    return self.mimetype
-                else:
-                    return 'none'
-            else:
-                return 'none'
-        else:
-            return self.mimetype
-
-    def __unicode__(self):
-        if self.item.title:
-            return self.item.title + ' - ' + self.mime_type
-        else:
-            return self.item.public_id + ' - ' + self.mime_type
-
-    class Meta(MetaCore):
-        db_table = app_name + '_media_transcoded'
-
-
-class MediaItemTranscodingFlag(ModelCore):
-    "Item flag to know if the MediaItem has been transcoded to a given format"
-
-    item            = ForeignKey('MediaItem', related_name="transcoding", verbose_name=_('item'))
-    mime_type       = CharField(_('mime_type'), required=True)
-    date            = DateTimeField(_('date'), auto_now=True)
-    value           = BooleanField(_('transcoded'))
-
-    class Meta(MetaCore):
-        db_table = 'media_transcoding'
-
-
-class DublinCoreToFormatMetadata(object):
-    """ a mapping class to get item DublinCore metadata dictionaries
-    in various audio metadata format (MP3, OGG, etc...)"""
-
-    #FIXME: should be given by timeside
-    unavailable_extensions = ['wav', 'aiff', 'aif', 'flac', 'webm']
-
-    metadata_mapping = {
-                    'mp3' : {
-                         'title': 'TIT2', #title2
-                         'creator': 'TCOM', #composer
-                         'creator': 'TPE1', #lead
-                         'identifier': 'UFID', #unique ID
-                         'relation': 'TALB', #album
-                         'type': 'TCON', #genre
-                         'publisher': 'TPUB', #publisher
-                         'date': 'TDRC', #year
-#                         'coverage': 'COMM',  #comment
-                         },
-                    'ogg': {
-                        'creator': 'artist',
-                        'relation': 'album',
-                        'all': 'all',
-                       },
-                    'flac': {
-                        'creator': 'artist',
-                        'relation': 'album',
-                        'all': 'all',
-                       },
-                    'wav': {
-                        'creator': 'artist',
-                        'relation': 'album',
-                        'all': 'all',
-                       },
-                    'webm': {
-                        'creator': 'artist',
-                        'relation': 'album',
-                        'all': 'all',
-                       },
-                    }
-
-    def __init__(self, format):
-        self.format = format
-
-    def get_metadata(self, dc_metadata):
-        mapp = self.metadata_mapping[self.format]
-        metadata = {}
-        keys_done = []
-        for data in dc_metadata:
-            key = data[0]
-            value = data[1].encode('utf-8')
-            if value:
-                if key == 'date':
-                    value = value.split(';')[0].split('=')
-                    if len(value) > 1:
-                        value  = value[1]
-                        value = value.split('-')[0]
-                    else:
-                        value = value[0].split('-')[0]
-                if key in mapp:
-                    metadata[mapp[key]] = value.decode('utf-8')
-                elif 'all' in mapp.keys():
-                    metadata[key] = value.decode('utf-8')
-                keys_done.append(key)
-        return metadata
-
-
-class MediaCorpus(MediaBaseResource):
-    "Describe a corpus"
-
-    element_type = 'corpus'
-    children_type = 'collections'
-
-    children = models.ManyToManyField(MediaCollection, related_name="corpus",
-                                      verbose_name=_('collections'),  blank=True, null=True)
-    recorded_from_year    = IntegerField(_('recording year (from)'), help_text=_('YYYY'))
-    recorded_to_year      = IntegerField(_('recording year (until)'), help_text=_('YYYY'))
-
-    objects = MediaCorpusManager()
-
-    @property
-    def public_id(self):
-        return self.code
-
-    @property
-    def has_mediafile(self):
-        for child in self.children.all():
-            if child.has_mediafile:
-                return True
-        return False
-
-    def computed_duration(self):
-        duration = Duration()
-        for child in self.children.all():
-            duration += child.computed_duration()
-        return duration
-    computed_duration.verbose_name = _('total available duration')
-
-    class Meta(MetaCore):
-        db_table = 'media_corpus'
-        verbose_name = _('corpus')
-        verbose_name_plural = _('corpus')
-        ordering = ['code']
-
-
-class MediaFonds(MediaBaseResource):
-    "Describe fonds"
-
-    element_type = 'fonds'
-    children_type = 'corpus'
-
-    children = models.ManyToManyField(MediaCorpus, related_name="fonds",
-                                      verbose_name=_('corpus'), blank=True, null=True)
-
-    objects = MediaFondsManager()
-
-    @property
-    def public_id(self):
-        return self.code
-
-    @property
-    def has_mediafile(self):
-        for child in self.children.all():
-            if child.has_mediafile:
-                return True
-        return False
-
-    def computed_duration(self):
-        duration = Duration()
-        for child in self.children.all():
-            duration += child.computed_duration()
-        return duration
-    computed_duration.verbose_name = _('total available duration')
-
-    class Meta(MetaCore):
-        db_table = 'media_fonds'
-        verbose_name = _('fonds')
-        verbose_name_plural = _('fonds')
-        ordering = ['code']
-
-
-class MediaCorpusRelated(MediaRelated):
-    "Corpus related media"
-
-    resource = ForeignKey(MediaCorpus, related_name="related", verbose_name=_('corpus'))
-
-    class Meta(MetaCore):
-        db_table = 'media_corpus_related'
-        verbose_name = _('corpus related media')
-        verbose_name_plural = _('corpus related media')
-
-
-class MediaFondsRelated(MediaRelated):
-    "Fonds related media"
-
-    resource = ForeignKey(MediaFonds, related_name="related", verbose_name=_('fonds'))
-
-    class Meta(MetaCore):
-        db_table = 'media_fonds_related'
-        verbose_name = _('fonds related media')
-        verbose_name_plural = _('fonds related media')
-
-
-class Identifier(ModelCore):
-    """Resource identifier"""
-
-    identifier = CharField(_('identifier'), max_length=255, blank=True, unique=True)
-    type = WeakForeignKey('IdentifierType', verbose_name=_('type'))
-    date_add = DateTimeField(_('date added'), auto_now_add=True)
-    date_first = DateTimeField(_('date of first attribution'))
-    date_last = DateTimeField(_('date of last attribution'))
-    date_modified = DateTimeField(_('date modified'), auto_now=True)
-    notes = TextField(_('notes'))
-
-    class Meta(MetaCore):
-        abstract = True
-        ordering = ['-date_last']
-
-
-class MediaItemIdentifier(Identifier):
-    """Item identifier"""
-
-    item = ForeignKey(MediaItem, related_name="identifiers", verbose_name=_('item'))
-
-    class Meta(MetaCore):
-        db_table = 'media_item_identifier'
-        verbose_name = _('item identifier')
-        verbose_name_plural = _('item identifiers')
-        unique_together = ('identifier', 'item')
-
-
-class MediaCollectionIdentifier(Identifier):
-    """Collection identifier"""
-
-    collection = ForeignKey(MediaCollection, related_name="identifiers", verbose_name=_('collection'))
-
-    class Meta(MetaCore):
-        db_table = 'media_collection_identifier'
-        verbose_name = _('collection identifier')
-        verbose_name_plural = _('collection identifiers')
-        unique_together = ('identifier', 'collection')
-
diff --git a/telemeta/models/playlist.py b/telemeta/models/playlist.py
new file mode 100644 (file)
index 0000000..fbae06b
--- /dev/null
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2010 Samalyse SARL
+# Copyright (C) 2010-2014 Parisson SARL
+
+# This software is a computer program whose purpose is to backup, analyse,
+# transcode and stream any audio content with its metadata over a web frontend.
+
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+#
+# Authors: Olivier Guilyardi <olivier@samalyse.com>
+#          David LIPSZYC <davidlipszyc@gmail.com>
+#          Guillaume Pellerin <yomguy@parisson.com>
+
+
+from __future__ import division
+from django.utils.translation import ugettext_lazy as _
+from telemeta.models.core import *
+
+
+class Playlist(ModelCore):
+    "Item, collection or marker playlist"
+    element_type = 'playlist'
+    public_id      = CharField(_('public_id'), required=True)
+    author         = ForeignKey(User, related_name="playlists", db_column="author")
+    title          = CharField(_('title'), required=True)
+    description    = TextField(_('description'))
+
+    class Meta(MetaCore):
+        db_table = 'playlists'
+
+    def __unicode__(self):
+        return self.title
+
+
+class PlaylistResource(ModelCore):
+    "Playlist components"
+    RESOURCE_TYPE_CHOICES = (('item', 'item'), ('collection', 'collection'),
+                             ('marker', 'marker'), ('fonds', 'fonds'), ('corpus', 'corpus'))
+    element_type       = 'playlist_resource'
+    public_id          = CharField(_('public_id'), required=True)
+    playlist           = ForeignKey('Playlist', related_name="resources", verbose_name=_('playlist'))
+    resource_type      = CharField(_('resource_type'), choices=RESOURCE_TYPE_CHOICES, required=True)
+    resource_id        = CharField(_('resource_id'), required=True)
+
+    class Meta(MetaCore):
+        db_table = 'playlist_resources'
+
+
diff --git a/telemeta/models/resource.py b/telemeta/models/resource.py
new file mode 100644 (file)
index 0000000..3939254
--- /dev/null
@@ -0,0 +1,137 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2010 Samalyse SARL
+# Copyright (C) 2010-2014 Parisson SARL
+
+# This software is a computer program whose purpose is to backup, analyse,
+# transcode and stream any audio content with its metadata over a web frontend.
+
+# This software is governed by the CeCILL  license under French law and
+# abiding by the rules of distribution of free software.  You can  use,
+# modify and/ or redistribute the software under the terms of the CeCILL
+# license as circulated by CEA, CNRS and INRIA at the following URL
+# "http://www.cecill.info".
+
+# As a counterpart to the access to the source code and  rights to copy,
+# modify and redistribute granted by the license, users are provided only
+# with a limited warranty  and the software's author,  the holder of the
+# economic rights,  and the successive licensors  have only  limited
+# liability.
+
+# In this respect, the user's attention is drawn to the risks associated
+# with loading,  using,  modifying and/or developing or reproducing the
+# software by the user in light of its specific status of free software,
+# that may mean  that it is complicated to manipulate,  and  that  also
+# therefore means  that it is reserved for developers  and  experienced
+# professionals having in-depth computer knowledge. Users are therefore
+# encouraged to load and test the software's suitability as regards their
+# requirements in conditions enabling the security of their systems and/or
+# data to be ensured and,  more generally, to use and operate it in the
+# same conditions as regards security.
+
+# The fact that you are presently reading this means that you have had
+# knowledge of the CeCILL license and that you accept its terms.
+#
+# Authors: Olivier Guilyardi <olivier@samalyse.com>
+#          David LIPSZYC <davidlipszyc@gmail.com>
+#          Guillaume Pellerin <yomguy@parisson.com>
+
+
+from django.utils.translation import ugettext_lazy as _
+from telemeta.models.core import *
+
+
+class MediaResource(ModelCore):
+    "Base class of all media objects"
+
+    def public_access_label(self):
+        if self.public_access == 'metadata':
+            return _('Metadata only')
+        elif self.public_access == 'full':
+            return _('Sound and metadata')
+
+        return _('Private data')
+    public_access_label.verbose_name = _('access type')
+
+    def set_revision(self, user):
+        "Save a media object and add a revision"
+        Revision.touch(self, user)
+
+    def get_revision(self):
+        return Revision.objects.filter(element_type=self.element_type, element_id=self.id).order_by('-time')[0]
+
+    class Meta:
+        abstract = True
+
+
+class MediaBaseResource(MediaResource):
+    "Describe a media base resource"
+
+    title                 = CharField(_('title'), required=True)
+    description           = CharField(_('description_old'))
+    descriptions          = TextField(_('description'))
+    code                  = CharField(_('code'), unique=True, required=True)
+    public_access         = CharField(_('public access'), choices=PUBLIC_ACCESS_CHOICES, max_length=16, default="metadata")
+
+    def __unicode__(self):
+        return self.code
+
+    @property
+    def public_id(self):
+        return self.code
+
+    def save(self, force_insert=False, force_update=False, user=None, code=None):
+        super(MediaBaseResource, self).save(force_insert, force_update)
+
+    def get_fields(self):
+        return self._meta.fields
+
+    class Meta(MetaCore):
+        abstract = True
+
+
+class MediaRelated(MediaResource):
+    "Related media"
+
+    element_type = 'media'
+
+    title           = CharField(_('title'))
+    date            = DateTimeField(_('date'), auto_now=True)
+    description     = TextField(_('description'))
+    mime_type       = CharField(_('mime_type'))
+    url             = CharField(_('url'), max_length=500)
+    credits         = CharField(_('credits'))
+    file            = FileField(_('file'), upload_to='items/%Y/%m/%d', db_column="filename", max_length=255)
+
+    def is_image(self):
+        is_url_image = False
+        if self.url:
+            url_types = ['.png', '.jpg', '.gif', '.jpeg']
+            for type in url_types:
+                if type in self.url or type.upper() in self.url:
+                    is_url_image = True
+        return 'image' in self.mime_type or is_url_image
+
+    def save(self, force_insert=False, force_update=False, author=None):
+        super(MediaRelated, self).save(force_insert, force_update)
+
+    def set_mime_type(self):
+        if self.file:
+            self.mime_type = mimetypes.guess_type(self.file.path)[0]
+
+    def is_kdenlive_session(self):
+        if self.file:
+            return '.kdenlive' in self.file.path
+        else:
+            return False
+
+    def __unicode__(self):
+        if self.title and not re.match('^ *N *$', self.title):
+            title = self.title
+        else:
+            title = unicode(self.item)
+        return title
+
+    class Meta:
+        abstract = True
+
+
diff --git a/telemeta/models/utils.py b/telemeta/models/utils.py
new file mode 100644 (file)
index 0000000..70e71f3
--- /dev/null
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2010-2014 Parisson SARL
+
+
+def get_random_hash():
+    hash = random.getrandbits(64)
+    return "%016x" % hash
+
+
+def get_full_url(path):
+    return 'http://' + Site.objects.get_current().domain + path
+
+
+def word_search_q(field, pattern):
+    words = re.split("[ .*-]+", pattern)
+    q = Q()
+    for w in words:
+        if len(w) >= 3:
+            kwargs = {field + '__icontains': w}
+            q &= Q(**kwargs)
+
+    return q