source: telemeta/models/media.py @ 41c6db0

cremcrem2devdev2diademsfeature/ts-0.5feature/ts-0.5.4feature/writecachemergesabiod
Last change on this file since 41c6db0 was 41c6db0, checked in by yomguy <yomguy@…>, 2 years ago

Merge branch 'master' into crem

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