source: telemeta/models/media.py @ bf87cda

cremcrem2crem3devdev2diademsdj1.6feature/breadcrumbsfeature/ts-0.5feature/ts-0.5.4feature/writecachegenericinstru_searchlamlam2mapsv3mergenlivemultiprobrelease/1.4.4sabiodsearchsecurityserversocialstoragetelecastertest
Last change on this file since bf87cda was bf87cda, checked in by yomguy <yomguy@…>, 3 years ago

Merge branch 'master' into dev

  • Property mode set to 100644
File size: 27.4 KB
Line 
1# -*- coding: utf-8 -*-
2# Copyright (C) 2007-2010 Samalyse SARL
3# Copyright (C) 2010-2011 Parisson SARL
4
5# This software is a computer program whose purpose is to backup, analyse,
6# transcode and stream any audio content with its metadata over a web frontend.
7
8# This software is governed by the CeCILL  license under French law and
9# abiding by the rules of distribution of free software.  You can  use,
10# modify and/ or redistribute the software under the terms of the CeCILL
11# license as circulated by CEA, CNRS and INRIA at the following URL
12# "http://www.cecill.info".
13
14# As a counterpart to the access to the source code and  rights to copy,
15# modify and redistribute granted by the license, users are provided only
16# with a limited warranty  and the software's author,  the holder of the
17# economic rights,  and the successive licensors  have only  limited
18# liability.
19
20# In this respect, the user's attention is drawn to the risks associated
21# with loading,  using,  modifying and/or developing or reproducing the
22# software by the user in light of its specific status of free software,
23# that may mean  that it is complicated to manipulate,  and  that  also
24# therefore means  that it is reserved for developers  and  experienced
25# professionals having in-depth computer knowledge. Users are therefore
26# encouraged to load and test the software's suitability as regards their
27# requirements in conditions enabling the security of their systems and/or
28# data to be ensured and,  more generally, to use and operate it in the
29# same conditions as regards security.
30
31# The fact that you are presently reading this means that you have had
32# knowledge of the CeCILL license and that you accept its terms.
33#
34# Authors: Olivier Guilyardi <olivier@samalyse.com>
35#          David LIPSZYC <davidlipszyc@gmail.com>
36#          Guillaume Pellerin <yomguy@parisson.com>
37
38from django.contrib.auth.models import User
39from django.utils.translation import ugettext_lazy as _
40from django.core.exceptions import ValidationError
41from telemeta.models.core import *
42from telemeta.models.enum import ContextKeyword
43from telemeta.util.unaccent import unaccent_icmp
44import re
45import mimetypes
46from telemeta.models.location import LocationRelation, Location
47from telemeta.models.system import Revision
48from telemeta.models.query import *
49from telemeta.models.instrument import *
50from telemeta.models.enum import *
51from telemeta.models.language import *
52from django.db import models
53
54collection_published_code_regex   = '[A-Za-z0-9._-]*'
55collection_unpublished_code_regex = '[A-Za-z0-9._-]*'
56collection_code_regex             = '(?:%s|%s)' % (collection_published_code_regex,
57                                                    collection_unpublished_code_regex)
58
59item_published_code_regex    = '[A-Za-z0-9._-]*'
60item_unpublished_code_regex  = '[A-Za-z0-9._-]*'
61item_code_regex              = '(?:%s|%s)' % (item_published_code_regex, item_unpublished_code_regex)
62
63PUBLIC_ACCESS_CHOICES = (('none', 'none'), ('metadata', 'metadata'), ('full', 'full'))
64
65
66class MediaResource(ModelCore):
67    "Base class of all media objects"
68
69    def public_access_label(self):
70        if self.public_access == 'metadata':
71            return _('Metadata only')
72        elif self.public_access == 'full':
73            return _('Sound and metadata')
74
75        return _('Private data')
76    public_access_label.verbose_name = _('public access')
77
78    def set_revision(self, user):
79        "Save a media object and add a revision"
80        Revision.touch(self, user)
81
82    def get_revision(self):
83        return Revision.objects.filter(element_type=self.element_type, element_id=self.id).order_by('-time')[0]
84
85    class Meta:
86        abstract = True
87
88
89class MediaBaseResource(MediaResource):
90    "Describe a media base resource"
91
92    title                 = CharField(_('title'), required=True)
93    description           = CharField(_('description'))
94    code                  = CharField(_('code'), unique=True, required=True)
95    reference             = CharField(_('reference'), unique=True, null=True)
96    public_access         = CharField(_('public access'), choices=PUBLIC_ACCESS_CHOICES,
97                                      max_length=16, default="metadata")
98
99    def __unicode__(self):
100        return self.code
101
102    @property
103    def public_id(self):
104        return self.code
105
106    def save(self, force_insert=False, force_update=False, user=None, code=None):
107        super(MediaBaseResource, self).save(force_insert, force_update)
108
109    def get_fields(self):
110        return self._meta.fields
111
112    class Meta(MetaCore):
113        abstract = True
114        ordering = ['code']
115
116
117class MediaRelated(MediaResource):
118    "Related media"
119
120    element_type = 'media'
121
122    title           = CharField(_('title'))
123    date            = DateTimeField(_('date'), auto_now=True)
124    description     = TextField(_('description'))
125    mime_type       = CharField(_('mime_type'), null=True)
126    url             = CharField(_('url'), max_length=500)
127    credits         = CharField(_('credits'))
128    file            = FileField(_('file'), upload_to='items/%Y/%m/%d', db_column="filename")
129
130    def is_image(self):
131        is_url_image = False
132        if self.url:
133            url_types = ['.png', '.jpg', '.gif', '.jpeg']
134            for type in url_types:
135                if type in self.url or type.upper() in self.url:
136                    is_url_image = True
137        return 'image' in self.mime_type or is_url_image
138
139    def save(self, force_insert=False, force_update=False):
140        super(MediaRelated, self).save(force_insert, force_update)
141
142    def set_mime_type(self):
143        if self.file:
144            self.mime_type = mimetypes.guess_type(self.file.path)[0]
145
146    def __unicode__(self):
147        if self.title and not re.match('^ *N *$', self.title):
148            title = self.title
149        else:
150            title = unicode(self.item)
151        return title
152
153    class Meta:
154        abstract = True
155
156
157class MediaCollection(MediaResource):
158    "Describe a collection of items"
159
160    element_type = 'collection'
161
162    def is_valid_collection_code(value):
163        "Check if the collection code is well formed"
164        regex = '^' + collection_code_regex + '$'
165        if not re.match(regex, value):
166            raise ValidationError(u'%s is not a valid collection code' % value)
167
168    # General informations
169    reference             = CharField(_('reference'), unique=True, null=True)
170    title                 = CharField(_('title'), required=True)
171    alt_title             = CharField(_('original title / translation'))
172    creator               = CharField(_('depositor / contributor'))
173    recording_context     = WeakForeignKey('RecordingContext', related_name="collections",
174                                           verbose_name=_('recording context'))
175    recorded_from_year    = IntegerField(_('recording year (from)'))
176    recorded_to_year      = IntegerField(_('recording year (until)'))
177    year_published        = IntegerField(_('year published'))
178
179    # Geographic and cultural informations
180    ## See "countries" and "ethnic_groups" methods below
181
182    # Legal notices
183    collector             = CharField(_('recordist'))
184    publisher             = WeakForeignKey('Publisher', related_name="collections",
185                                           verbose_name=_('publisher / status'))
186    publisher_collection  = WeakForeignKey('PublisherCollection', related_name="collections",
187                                            verbose_name=_('publisher collection'))
188    publisher_serial      = CharField(_('publisher serial number'))
189    booklet_author        = CharField(_('author of published notice'))
190    external_references   = TextField(_('bibliographic references'))
191    doctype_code          = IntegerField(_('document type'))
192    public_access         = CharField(_('public access'), choices=PUBLIC_ACCESS_CHOICES,
193                                      max_length=16, default="metadata")
194    legal_rights          = WeakForeignKey('LegalRight', related_name="collections",
195                                           verbose_name=_('legal rights'))
196
197    # Archiving data
198    acquisition_mode      = WeakForeignKey('AcquisitionMode', related_name="collections",
199                                            verbose_name=_('mode of acquisition'))
200    cnrs_contributor      = CharField(_('CNRS depositor'))
201    metadata_author       = WeakForeignKey('MetadataAuthor', related_name="collections",
202                                           verbose_name=_('record author'))
203    booklet_description   = TextField(_('related documentation'))
204    publishing_status     = WeakForeignKey('PublishingStatus', related_name="collections",
205                                           verbose_name=_('secondary edition'))
206    alt_ids               = CharField(_('copies'))
207    comment               = TextField(_('comment'))
208    metadata_writer       = WeakForeignKey('MetadataWriter', related_name="collections",
209                                           verbose_name=_('record writer'))
210    travail               = CharField(_('archiver notes'))
211    items_done            = CharField(_('items finished'))
212    collector_is_creator  = BooleanField(_('recordist identical to depositor'))
213    is_published          = BooleanField(_('published'))
214    conservation_site     = CharField(_('conservation site'))
215
216    # Technical data
217    code                  = CharField(_('code'), unique=True, required=True, validators=[is_valid_collection_code])
218    old_code              = CharField(_('old code'), unique=False, null=True, blank=True)
219    approx_duration       = DurationField(_('approximative duration'))
220    physical_items_num    = IntegerField(_('number of components (medium / piece)'))
221    physical_format       = WeakForeignKey('PhysicalFormat', related_name="collections",
222                                           verbose_name=_('archive format'))
223    ad_conversion         = WeakForeignKey('AdConversion', related_name='collections',
224                                           verbose_name=_('digitization'))
225    state                 = TextField(_('status'))
226    a_informer_07_03      = CharField(_('a_informer_07_03'))
227
228    # All
229    objects               = MediaCollectionManager()
230
231    def __unicode__(self):
232        return self.code
233
234    @property
235    def public_id(self):
236        return self.code
237
238    @property
239    def has_mediafile(self):
240        "Tell wether this collection has any media files attached to its items"
241        items = self.items.all()
242        for item in items:
243            if item.file:
244                return True
245        return False
246
247    def __name_cmp(self, obj1, obj2):
248        return unaccent_icmp(obj1.name, obj2.name)
249
250    def countries(self):
251        "Return the countries of the items"
252        countries = []
253        for item in self.items.filter(location__isnull=False):
254            for country in item.location.countries():
255                if not country in countries:
256                    countries.append(country)
257
258        countries.sort(self.__name_cmp)
259
260        return countries
261    countries.verbose_name = _("states / nations")
262
263    def ethnic_groups(self):
264        "Return the ethnic groups of the items"
265        groups = []
266        items = self.items.all()
267        for item in items:
268            if item.ethnic_group and not item.ethnic_group in groups:
269                groups.append(item.ethnic_group)
270
271        cmp = lambda a, b: unaccent_icmp(a.value, b.value)
272        groups.sort(cmp)
273
274        return groups
275    ethnic_groups.verbose_name = _('populations / social groups')
276
277    def computed_duration(self):
278        duration = Duration()
279        for item in self.items.all():
280            duration += item.computed_duration()
281        return duration
282
283    computed_duration.verbose_name = _('computed duration')
284
285    def save(self, force_insert=False, force_update=False, user=None, code=None):
286        super(MediaCollection, self).save(force_insert, force_update)
287
288    class Meta(MetaCore):
289        db_table = 'media_collections'
290        ordering = ['code']
291        verbose_name = _('collection')
292
293
294class MediaCollectionRelated(MediaRelated):
295    "Collection related media"
296
297    collection      = ForeignKey('MediaCollection', related_name="related", verbose_name=_('collection'))
298
299    class Meta(MetaCore):
300        db_table = 'media_collection_related'
301        verbose_name = _('collection related media')
302        verbose_name_plural = _('collection related media')
303
304
305class MediaItem(MediaResource):
306    "Describe an item"
307
308    element_type = 'item'
309
310    # Main Informations
311    title                 = CharField(_('title'))
312    alt_title             = CharField(_('original title / translation'))
313    collector             = CharField(_('recordist'))
314    collection            = ForeignKey('MediaCollection', related_name="items",
315                                       verbose_name=_('collection'))
316    recorded_from_date    = DateField(_('recording date (from)'))
317    recorded_to_date      = DateField(_('recording date (until)'))
318
319    # Geographic and cultural informations
320    location              = WeakForeignKey('Location', verbose_name=_('location'))
321    location_comment      = CharField(_('location details'))
322    cultural_area         = CharField(_('cultural area'))
323    ethnic_group          = WeakForeignKey('EthnicGroup', related_name="items",
324                                           verbose_name=_('population / social group'))
325    language              = CharField(_('language'))
326    language_iso          = ForeignKey('Language', related_name="items",
327                                       verbose_name=_('ISO language'), blank=True,
328                                        null=True, on_delete=models.SET_NULL)
329    context_comment       = TextField(_('comments / ethnographic context'))
330    moda_execut           = CharField(_('moda_execut'))
331
332    # Musical informations
333    vernacular_style      = WeakForeignKey('VernacularStyle', related_name="items",
334                                           verbose_name=_('vernacular style'))
335    generic_style         = WeakForeignKey('GenericStyle', related_name="items",
336                                           verbose_name=_('generic style'))
337    author                = CharField(_('author / compositor'))
338
339    # General informations
340    comment               = TextField(_('remarks'))
341    collector_selection   = CharField(_('recordist selection'))
342    collector_from_collection = BooleanField(_('recordist as in collection'))
343
344    # Archiving data
345    code                  = CharField(_('code'), unique=True, blank=True)
346    old_code              = CharField(_('old code'), unique=False, blank=True)
347    track                 = CharField(_('item number'))
348    creator_reference     = CharField(_('reference'))
349    external_references   = TextField(_('published references'))
350    copied_from_item      = WeakForeignKey('self', related_name="copies", verbose_name=_('copy of'))
351    public_access         = CharField(_('public access'), choices=PUBLIC_ACCESS_CHOICES, max_length=16, default="metadata")
352    file                  = FileField(_('file'), upload_to='items/%Y/%m/%d', db_column="filename")
353
354    # Technical data
355    approx_duration       = DurationField(_('approximative duration'))
356
357    # All
358    objects               = MediaItemManager()
359
360    def keywords(self):
361        return ContextKeyword.objects.filter(item_relations__item = self)
362    keywords.verbose_name = _('keywords')
363
364    @property
365    def public_id(self):
366        if self.code:
367            return self.code
368        return self.id
369
370    class Meta(MetaCore):
371        db_table = 'media_items'
372        permissions = (("can_play_all_items", "Can play all media items"),
373                       ("can_download_all_items", "Can download all media items"), )
374        verbose_name = _('item')
375
376    def is_valid_code(self, code):
377        "Check if the item code is well formed"
378        if not re.match('^' + self.collection.code, self.code):
379            return False
380        if self.collection.is_published:
381            regex = '^' + item_published_code_regex + '$'
382        else:
383            regex = '^' + item_unpublished_code_regex + '$'
384        if re.match(regex, code):
385            return True
386        return False
387
388    def clean(self):
389        if self.code and not self.is_valid_code(self.code):
390            raise ValidationError("%s is not a valid item code for collection %s"
391                                        % (self.code, self.collection.code))
392
393    def save(self, force_insert=False, force_update=False):
394        super(MediaItem, self).save(force_insert, force_update)
395
396    def computed_duration(self):
397        "Tell the length in seconds of this item media data"
398        return self.approx_duration
399
400    computed_duration.verbose_name = _('computed duration')
401
402    def __unicode__(self):
403        if self.title and not re.match('^ *N *$', self.title):
404            title = self.title
405        else:
406            title = unicode(self.collection)
407        if self.track:
408            title += ' ' + self.track
409        return title
410
411    @property
412    def instruments(self):
413        "Return the instruments of the item"
414        instruments = []
415        performances = MediaItemPerformance.objects.filter(media_item=self)
416        for performance in performances:
417            instrument = performance.instrument
418            alias = performance.alias
419            if not instrument in instruments:
420                instruments.append(instrument)
421            if not alias in instruments:
422                instruments.append(alias)
423
424        instruments.sort(self.__name_cmp)
425        return instruments
426
427        instruments.verbose_name = _("instruments")
428
429
430class MediaItemRelated(MediaRelated):
431    "Item related media"
432
433    item            = ForeignKey('MediaItem', related_name="related", verbose_name=_('item'))
434
435    class Meta(MetaCore):
436        db_table = 'media_item_related'
437        verbose_name = _('item related media')
438        verbose_name_plural = _('item related media')
439
440
441class MediaItemKeyword(ModelCore):
442    "Item keyword"
443    item    = ForeignKey('MediaItem', verbose_name=_('item'), related_name="keyword_relations")
444    keyword = ForeignKey('ContextKeyword', verbose_name=_('keyword'), related_name="item_relations")
445
446    class Meta(MetaCore):
447        db_table = 'media_item_keywords'
448        unique_together = (('item', 'keyword'),)
449
450
451class MediaItemPerformance(ModelCore):
452    "Item performance"
453    media_item      = ForeignKey('MediaItem', related_name="performances",
454                                 verbose_name=_('item'))
455    instrument      = WeakForeignKey('Instrument', related_name="performances",
456                                     verbose_name=_('composition'))
457    alias           = WeakForeignKey('InstrumentAlias', related_name="performances",
458                                     verbose_name=_('vernacular name'))
459    instruments_num = CharField(_('number'))
460    musicians       = CharField(_('interprets'))
461
462    class Meta(MetaCore):
463        db_table = 'media_item_performances'
464
465
466class MediaItemAnalysis(ModelCore):
467    "Item analysis result computed by TimeSide"
468
469    element_type = 'analysis'
470    item  = ForeignKey('MediaItem', related_name="analysis", verbose_name=_('item'))
471    analyzer_id = CharField(_('id'), required=True)
472    name = CharField(_('name'))
473    value = CharField(_('value'))
474    unit = CharField(_('unit'))
475
476    class Meta(MetaCore):
477        db_table = 'media_analysis'
478        ordering = ['name']
479
480    def to_dict(self):
481        if self.analyzer_id == 'duration':
482            if '.' in self.value:
483                value = self.value.split('.')
484                self.value = '.'.join([value[0], value[1][:2]])
485        return {'id': self.analyzer_id, 'name': self.name, 'value': self.value, 'unit': self.unit}
486
487
488class MediaPart(MediaResource):
489    "Describe an item part"
490    element_type = 'part'
491    item  = ForeignKey('MediaItem', related_name="parts", verbose_name=_('item'))
492    title = CharField(_('title'), required=True)
493    start = FloatField(_('start'), required=True)
494    end   = FloatField(_('end'), required=True)
495
496    class Meta(MetaCore):
497        db_table = 'media_parts'
498        verbose_name = _('item part')
499
500    def __unicode__(self):
501        return self.title
502
503class Playlist(ModelCore):
504    "Item, collection or marker playlist"
505    element_type = 'playlist'
506    public_id      = CharField(_('public_id'), required=True)
507    author         = ForeignKey(User, related_name="playlists", db_column="author")
508    title          = CharField(_('title'), required=True)
509    description    = TextField(_('description'))
510
511    class Meta(MetaCore):
512        db_table = 'playlists'
513
514    def __unicode__(self):
515        return self.title
516
517
518class PlaylistResource(ModelCore):
519    "Playlist components"
520    RESOURCE_TYPE_CHOICES = (('item', 'item'), ('collection', 'collection'), ('marker', 'marker'), ('fonds', 'fonds'), ('corpus', 'corpus'))
521    element_type = 'playlist_resource'
522    public_id          = CharField(_('public_id'), required=True)
523    playlist           = ForeignKey('Playlist', related_name="resources", verbose_name=_('playlist'))
524    resource_type      = CharField(_('resource_type'), choices=RESOURCE_TYPE_CHOICES, required=True)
525    resource_id        = CharField(_('resource_id'), required=True)
526
527    class Meta(MetaCore):
528        db_table = 'playlist_resources'
529
530
531class MediaItemMarker(MediaResource):
532    "2D marker object : text value vs. time"
533
534    element_type = 'marker'
535
536    item            = ForeignKey('MediaItem', related_name="markers", verbose_name=_('item'))
537    public_id       = CharField(_('public_id'), required=True)
538    time            = FloatField(_('time'))
539    title           = CharField(_('title'))
540    date            = DateTimeField(_('date'), auto_now=True)
541    description     = TextField(_('description'))
542    author          = ForeignKey(User, related_name="markers", verbose_name=_('author'))
543
544    class Meta(MetaCore):
545        db_table = 'media_markers'
546
547    def __unicode__(self):
548        if self.title:
549            return self.title
550        else:
551            return self.public_id
552
553
554class MediaItemTranscodingFlag(ModelCore):
555    "Item flag to know if the MediaItem has been transcoded to a given format"
556
557    item            = ForeignKey('MediaItem', related_name="transcoding", verbose_name=_('item'))
558    mime_type       = CharField(_('mime_type'), required=True)
559    date            = DateTimeField(_('date'), auto_now=True)
560    value           = BooleanField(_('transcoded'))
561
562    class Meta(MetaCore):
563        db_table = 'media_transcoding'
564
565
566class DublinCoreToFormatMetadata(object):
567    """ a mapping class to get item DublinCore metadata dictionaries
568    in various audio metadata format (MP3, OGG, etc...)"""
569
570    #FIXME: should be given by timeside
571    unavailable_extensions = ['wav', 'aiff', 'aif', 'flac', 'webm']
572
573    metadata_mapping = {
574                    'mp3' : {
575                         'title': 'TIT2', #title2
576                         'creator': 'TCOM', #composer
577                         'creator': 'TPE1', #lead
578                         'identifier': 'UFID', #unique ID
579                         'relation': 'TALB', #album
580                         'type': 'TCON', #genre
581                         'publisher': 'TPUB', #publisher
582                         'date': 'TDRC', #year
583#                         'coverage': 'COMM',  #comment
584                         },
585                    'ogg': {
586                        'creator': 'artist',
587                        'relation': 'album',
588                        'all': 'all',
589                       },
590                    'flac': {
591                        'creator': 'artist',
592                        'relation': 'album',
593                        'all': 'all',
594                       },
595                    'wav': {
596                        'creator': 'artist',
597                        'relation': 'album',
598                        'all': 'all',
599                       },
600                    'webm': {
601                        'creator': 'artist',
602                        'relation': 'album',
603                        'all': 'all',
604                       },
605                    }
606
607    def __init__(self, format):
608        self.format = format
609
610    def get_metadata(self, dc_metadata):
611        mapp = self.metadata_mapping[self.format]
612        metadata = {}
613        keys_done = []
614        for data in dc_metadata:
615            key = data[0]
616            value = data[1].encode('utf-8')
617            if value:
618                if key == 'date':
619                    value = value.split(';')[0].split('=')
620                    if len(value) > 1:
621                        value  = value[1]
622                        value = value.split('-')[0]
623                    else:
624                        value = value[0].split('-')[0]
625                if key in mapp:
626                    metadata[mapp[key]] = value.decode('utf-8')
627                elif 'all' in mapp.keys():
628                    metadata[key] = value.decode('utf-8')
629                keys_done.append(key)
630        return metadata
631
632
633class MediaCorpus(MediaBaseResource):
634    "Describe a corpus"
635
636    element_type = 'corpus'
637    children_type = 'collections'
638
639    children = models.ManyToManyField(MediaCollection, related_name="corpus", verbose_name=_('collections'),  blank=True, null=True)
640    recorded_from_year    = IntegerField(_('recording year (from)'))
641    recorded_to_year      = IntegerField(_('recording year (until)'))
642
643    objects = MediaCorpusManager()
644
645    @property
646    def public_id(self):
647        return self.code
648
649    class Meta(MetaCore):
650        db_table = 'media_corpus'
651        verbose_name = _('corpus')
652        verbose_name_plural = _('corpus')
653
654
655class MediaFonds(MediaBaseResource):
656    "Describe fonds"
657
658    element_type = 'fonds'
659    children_type = 'corpus'
660
661    children = models.ManyToManyField(MediaCorpus, related_name="fonds", verbose_name=_('corpus'), blank=True, null=True)
662
663    objects = MediaFondsManager()
664
665    @property
666    def public_id(self):
667        return self.code
668
669    class Meta(MetaCore):
670        db_table = 'media_fonds'
671        verbose_name = _('fonds')
672        verbose_name_plural = _('fonds')
673
674
675class MediaCorpusRelated(MediaRelated):
676    "Corpus related media"
677
678    resource = ForeignKey(MediaCorpus, related_name="related", verbose_name=_('corpus'))
679
680    class Meta(MetaCore):
681        db_table = 'media_corpus_related'
682        verbose_name = _('corpus related media')
683        verbose_name_plural = _('corpus related media')
684
685
686class MediaFondsRelated(MediaRelated):
687    "Fonds related media"
688
689    resource = ForeignKey(MediaFonds, related_name="related", verbose_name=_('fonds'))
690
691    class Meta(MetaCore):
692        db_table = 'media_fonds_related'
693        verbose_name = _('fonds related media')
694        verbose_name_plural = _('fonds related media')
695
696
697class Format(ModelCore):
698    """ Physical format object as proposed by the LAM"""
699
700    item = ForeignKey(MediaItem, related_name="formats", verbose_name=_('item'))
701    original_code = CharField(_('original code'), required=True)
702    tape_number = CharField(_('tape number'))
703    status = CharField(_('status'))
704    conservation_state = CharField(_('conservation state'))
705    comments = TextField(_('comments'))
706
707    tape_length = WeakForeignKey(TapeLength, related_name="formats", verbose_name = _("tape length (cm)"))
708    tape_width  = WeakForeignKey(TapeWidth, related_name="formats", verbose_name = _("tape width (inch)"))
709    tape_speed = WeakForeignKey(TapeSpeed, related_name="formats", verbose_name = _("tape speed (m/s)"))
710    tape_vendor = WeakForeignKey(TapeVendor, related_name="formats")
711    tape_thickness = CharField(_('tape thickness (um)'))
712    tape_diameter = CharField(_('tape diameter (mm)'))
713    tape_reference = CharField(_('tape reference'))
714
715    class Meta(MetaCore):
716        db_table = 'media_formats'
717        verbose_name = _('format')
718
719    def __unicode__(self):
720        return self.original_code
721
722    @property
723    def public_id(self):
724        return self.original_code
725
Note: See TracBrowser for help on using the repository browser.