source: telemeta/models/media.py @ fabced9

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

add extra blocks, horizontal window for children in admin

  • Property mode set to 100644
File size: 26.8 KB
Line 
1# -*- coding: utf-8 -*-
2# Copyright (C) 2007-2010 Samalyse SARL
3# Copyright (C) 2010-2011 Parisson SARL
4
5# This software is a computer program whose purpose is to backup, analyse,
6# transcode and stream any audio content with its metadata over a web frontend.
7
8# This software is governed by the CeCILL  license under French law and
9# abiding by the rules of distribution of free software.  You can  use,
10# modify and/ or redistribute the software under the terms of the CeCILL
11# license as circulated by CEA, CNRS and INRIA at the following URL
12# "http://www.cecill.info".
13
14# As a counterpart to the access to the source code and  rights to copy,
15# modify and redistribute granted by the license, users are provided only
16# with a limited warranty  and the software's author,  the holder of the
17# economic rights,  and the successive licensors  have only  limited
18# liability.
19
20# In this respect, the user's attention is drawn to the risks associated
21# with loading,  using,  modifying and/or developing or reproducing the
22# software by the user in light of its specific status of free software,
23# that may mean  that it is complicated to manipulate,  and  that  also
24# therefore means  that it is reserved for developers  and  experienced
25# professionals having in-depth computer knowledge. Users are therefore
26# encouraged to load and test the software's suitability as regards their
27# requirements in conditions enabling the security of their systems and/or
28# data to be ensured and,  more generally, to use and operate it in the
29# same conditions as regards security.
30
31# The fact that you are presently reading this means that you have had
32# knowledge of the CeCILL license and that you accept its terms.
33#
34# Authors: Olivier Guilyardi <olivier@samalyse.com>
35#          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
412class MediaItemRelated(MediaRelated):
413    "Item related media"
414
415    item            = ForeignKey('MediaItem', related_name="related", verbose_name=_('item'))
416
417    class Meta(MetaCore):
418        db_table = 'media_item_related'
419        verbose_name = _('item related media')
420        verbose_name_plural = _('item related media')
421
422
423class MediaItemKeyword(ModelCore):
424    "Item keyword"
425    item    = ForeignKey('MediaItem', verbose_name=_('item'), related_name="keyword_relations")
426    keyword = ForeignKey('ContextKeyword', verbose_name=_('keyword'), related_name="item_relations")
427
428    class Meta(MetaCore):
429        db_table = 'media_item_keywords'
430        unique_together = (('item', 'keyword'),)
431
432
433class MediaItemPerformance(ModelCore):
434    "Item performance"
435    media_item      = ForeignKey('MediaItem', related_name="performances",
436                                 verbose_name=_('item'))
437    instrument      = WeakForeignKey('Instrument', related_name="performances",
438                                     verbose_name=_('composition'))
439    alias           = WeakForeignKey('InstrumentAlias', related_name="performances",
440                                     verbose_name=_('vernacular name'))
441    instruments_num = CharField(_('number'))
442    musicians       = CharField(_('interprets'))
443
444    class Meta(MetaCore):
445        db_table = 'media_item_performances'
446
447
448class MediaItemAnalysis(ModelCore):
449    "Item analysis result computed by TimeSide"
450
451    element_type = 'analysis'
452    item  = ForeignKey('MediaItem', related_name="analysis", verbose_name=_('item'))
453    analyzer_id = CharField(_('id'), required=True)
454    name = CharField(_('name'))
455    value = CharField(_('value'))
456    unit = CharField(_('unit'))
457
458    class Meta(MetaCore):
459        db_table = 'media_analysis'
460        ordering = ['name']
461
462    def to_dict(self):
463        if self.analyzer_id == 'duration':
464            if '.' in self.value:
465                value = self.value.split('.')
466                self.value = '.'.join([value[0], value[1][:2]])
467        return {'id': self.analyzer_id, 'name': self.name, 'value': self.value, 'unit': self.unit}
468
469
470class MediaPart(MediaResource):
471    "Describe an item part"
472    element_type = 'part'
473    item  = ForeignKey('MediaItem', related_name="parts", verbose_name=_('item'))
474    title = CharField(_('title'), required=True)
475    start = FloatField(_('start'), required=True)
476    end   = FloatField(_('end'), required=True)
477
478    class Meta(MetaCore):
479        db_table = 'media_parts'
480        verbose_name = _('item part')
481
482    def __unicode__(self):
483        return self.title
484
485class Playlist(ModelCore):
486    "Item, collection or marker playlist"
487    element_type = 'playlist'
488    public_id      = CharField(_('public_id'), required=True)
489    author         = ForeignKey(User, related_name="playlists", db_column="author")
490    title          = CharField(_('title'), required=True)
491    description    = TextField(_('description'))
492
493    class Meta(MetaCore):
494        db_table = 'playlists'
495
496    def __unicode__(self):
497        return self.title
498
499
500class PlaylistResource(ModelCore):
501    "Playlist components"
502    RESOURCE_TYPE_CHOICES = (('item', 'item'), ('collection', 'collection'), ('marker', 'marker'), ('fonds', 'fonds'), ('corpus', 'corpus'))
503    element_type = 'playlist_resource'
504    public_id          = CharField(_('public_id'), required=True)
505    playlist           = ForeignKey('Playlist', related_name="resources", verbose_name=_('playlist'))
506    resource_type      = CharField(_('resource_type'), choices=RESOURCE_TYPE_CHOICES, required=True)
507    resource_id        = CharField(_('resource_id'), required=True)
508
509    class Meta(MetaCore):
510        db_table = 'playlist_resources'
511
512
513class MediaItemMarker(MediaResource):
514    "2D marker object : text value vs. time"
515
516    element_type = 'marker'
517
518    item            = ForeignKey('MediaItem', related_name="markers", verbose_name=_('item'))
519    public_id       = CharField(_('public_id'), required=True)
520    time            = FloatField(_('time'))
521    title           = CharField(_('title'))
522    date            = DateTimeField(_('date'), auto_now=True)
523    description     = TextField(_('description'))
524    author          = ForeignKey(User, related_name="markers", verbose_name=_('author'))
525
526    class Meta(MetaCore):
527        db_table = 'media_markers'
528
529    def __unicode__(self):
530        if self.title:
531            return self.title
532        else:
533            return self.public_id
534
535
536class MediaItemTranscodingFlag(ModelCore):
537    "Item flag to know if the MediaItem has been transcoded to a given format"
538
539    item            = ForeignKey('MediaItem', related_name="transcoding", verbose_name=_('item'))
540    mime_type       = CharField(_('mime_type'), required=True)
541    date            = DateTimeField(_('date'), auto_now=True)
542    value           = BooleanField(_('transcoded'))
543
544    class Meta(MetaCore):
545        db_table = 'media_transcoding'
546
547
548class DublinCoreToFormatMetadata(object):
549    """ a mapping class to get item DublinCore metadata dictionaries
550    in various audio metadata format (MP3, OGG, etc...)"""
551
552    #FIXME: should be given by timeside
553    unavailable_extensions = ['wav', 'aiff', 'aif', 'flac', 'webm']
554
555    metadata_mapping = {
556                    'mp3' : {
557                         'title': 'TIT2', #title2
558                         'creator': 'TCOM', #composer
559                         'creator': 'TPE1', #lead
560                         'identifier': 'UFID', #unique ID
561                         'relation': 'TALB', #album
562                         'type': 'TCON', #genre
563                         'publisher': 'TPUB', #publisher
564                         'date': 'TDRC', #year
565#                         'coverage': 'COMM',  #comment
566                         },
567                    'ogg': {
568                        'creator': 'artist',
569                        'relation': 'album',
570                        'all': 'all',
571                       },
572                    'flac': {
573                        'creator': 'artist',
574                        'relation': 'album',
575                        'all': 'all',
576                       },
577                    'wav': {
578                        'creator': 'artist',
579                        'relation': 'album',
580                        'all': 'all',
581                       },
582                    'webm': {
583                        'creator': 'artist',
584                        'relation': 'album',
585                        'all': 'all',
586                       },
587                    }
588
589    def __init__(self, format):
590        self.format = format
591
592    def get_metadata(self, dc_metadata):
593        mapp = self.metadata_mapping[self.format]
594        metadata = {}
595        keys_done = []
596        for data in dc_metadata:
597            key = data[0]
598            value = data[1].encode('utf-8')
599            if value:
600                if key == 'date':
601                    value = value.split(';')[0].split('=')
602                    if len(value) > 1:
603                        value  = value[1]
604                        value = value.split('-')[0]
605                    else:
606                        value = value[0].split('-')[0]
607                if key in mapp:
608                    metadata[mapp[key]] = value.decode('utf-8')
609                elif 'all' in mapp.keys():
610                    metadata[key] = value.decode('utf-8')
611                keys_done.append(key)
612        return metadata
613
614
615class MediaCorpus(MediaBaseResource):
616    "Describe a corpus"
617
618    element_type = 'corpus'
619    children_type = 'collections'
620
621    children = models.ManyToManyField(MediaCollection, related_name="corpus", verbose_name=_('collections'),  blank=True, null=True)
622    recorded_from_year    = IntegerField(_('recording year (from)'))
623    recorded_to_year      = IntegerField(_('recording year (until)'))
624
625    objects = MediaCorpusManager()
626
627    @property
628    def public_id(self):
629        return self.code
630
631    class Meta(MetaCore):
632        db_table = 'media_corpus'
633        verbose_name = _('corpus')
634        verbose_name_plural = _('corpus')
635
636
637class MediaFonds(MediaBaseResource):
638    "Describe fonds"
639
640    element_type = 'fonds'
641    children_type = 'corpus'
642
643    children = models.ManyToManyField(MediaCorpus, related_name="fonds", verbose_name=_('corpus'), blank=True, null=True)
644
645    objects = MediaFondsManager()
646
647    @property
648    def public_id(self):
649        return self.code
650
651    class Meta(MetaCore):
652        db_table = 'media_fonds'
653        verbose_name = _('fonds')
654        verbose_name_plural = _('fonds')
655
656
657class MediaCorpusRelated(MediaRelated):
658    "Corpus related media"
659
660    resource = ForeignKey(MediaCorpus, related_name="related", verbose_name=_('corpus'))
661
662    class Meta(MetaCore):
663        db_table = 'media_corpus_related'
664        verbose_name = _('corpus related media')
665        verbose_name_plural = _('corpus related media')
666
667
668class MediaFondsRelated(MediaRelated):
669    "Fonds related media"
670
671    resource = ForeignKey(MediaFonds, related_name="related", verbose_name=_('fonds'))
672
673    class Meta(MetaCore):
674        db_table = 'media_fonds_related'
675        verbose_name = _('fonds related media')
676        verbose_name_plural = _('fonds related media')
677
678
679class Format(ModelCore):
680    """ Physical format object as proposed by the LAM"""
681
682    item = ForeignKey(MediaItem, related_name="formats", verbose_name=_('item'))
683    original_code = CharField(_('original code'), required=True)
684    tape_number = CharField(_('tape number'))
685    status = CharField(_('status'))
686    conservation_state = CharField(_('conservation state'))
687    comments = TextField(_('comments'))
688
689    tape_length = WeakForeignKey(TapeLength, related_name="formats", verbose_name = _("tape length (cm)"))
690    tape_width  = WeakForeignKey(TapeWidth, related_name="formats", verbose_name = _("tape width (inch)"))
691    tape_speed = WeakForeignKey(TapeSpeed, related_name="formats", verbose_name = _("tape speed (m/s)"))
692    tape_vendor = WeakForeignKey(TapeVendor, related_name="formats")
693    tape_thickness = CharField(_('tape thickness (um)'))
694    tape_diameter = CharField(_('tape diameter (mm)'))
695    tape_reference = CharField(_('tape reference'))
696
697    class Meta(MetaCore):
698        db_table = 'media_formats'
699        verbose_name = _('format')
700
701    def __unicode__(self):
702        return self.original_code
703
704    @property
705    def public_id(self):
706        return self.original_code
707
Note: See TracBrowser for help on using the repository browser.