source: telemeta/models/crem.py @ 00ee49f

cremcrem2crem3devdev2diademsdj1.6feature/breadcrumbsfeature/ts-0.5feature/ts-0.5.4feature/writecacheformagenericinstru_searchlamlam2mapsv3mergenlivemultiproductionrelease/1.4.4sabiodsearchsecurityserversocialstoragetelecastertestvideo
Last change on this file since 00ee49f was 00ee49f, checked in by olivier <>, 5 years ago

add internationalized verbose names to model fields, and gettext french translation ; update collection detail template

  • Property mode set to 100755
File size: 27.3 KB
Line 
1# -*- coding: utf-8 -*-
2# Copyright (C) 2007-2010 Samalyse SARL
3
4# This software is a computer program whose purpose is to backup, analyse,
5# transcode and stream any audio content with its metadata over a web frontend.
6
7# This software is governed by the CeCILL  license under French law and
8# abiding by the rules of distribution of free software.  You can  use,
9# modify and/ or redistribute the software under the terms of the CeCILL
10# license as circulated by CEA, CNRS and INRIA at the following URL
11# "http://www.cecill.info".
12
13# As a counterpart to the access to the source code and  rights to copy,
14# modify and redistribute granted by the license, users are provided only
15# with a limited warranty  and the software's author,  the holder of the
16# economic rights,  and the successive licensors  have only  limited
17# liability.
18
19# In this respect, the user's attention is drawn to the risks associated
20# with loading,  using,  modifying and/or developing or reproducing the
21# software by the user in light of its specific status of free software,
22# that may mean  that it is complicated to manipulate,  and  that  also
23# therefore means  that it is reserved for developers  and  experienced
24# professionals having in-depth computer knowledge. Users are therefore
25# encouraged to load and test the software's suitability as regards their
26# requirements in conditions enabling the security of their systems and/or
27# data to be ensured and,  more generally, to use and operate it in the
28# same conditions as regards security.
29
30# The fact that you are presently reading this means that you have had
31# knowledge of the CeCILL license and that you accept its terms.
32#
33# Authors: Olivier Guilyardi <olivier@samalyse.com>
34#          David LIPSZYC <davidlipszyc@gmail.com>
35
36from django.core.exceptions import ObjectDoesNotExist
37import cremquery as query
38from xml.dom.minidom import getDOMImplementation
39from telemeta.util.unaccent import unaccent_icmp
40import re
41from django.db.models import FieldDoesNotExist
42from telemeta.models.core import DurationField, Duration, WeakForeignKey, EnhancedModel, \
43                                 CharField, TextField, IntegerField, BooleanField, \
44                                 DateTimeField, FileField, ForeignKey, FloatField, DateField
45from telemeta.models import dublincore as dc
46from django.utils.translation import ugettext_lazy as _
47
48class ModelCore(EnhancedModel):
49
50    @classmethod
51    def required_fields(cls):
52        required = []
53        for field in cls._meta.fields:
54            if not field.blank:
55                required.append(field)
56        return required
57
58    def save(self, force_insert=False, force_update=False, using=None):
59        required = self.required_fields()
60        for field in required:
61            if not getattr(self, field.name):
62                raise RequiredFieldError(self, field)
63        super(ModelCore, self).save(force_insert, force_update, using)
64
65    def save_with_revision(self, user, force_insert=False, force_update=False, using=None):
66        "Save a media object and add a revision"
67        self.save(force_insert, force_update, using)
68        Revision.touch(self, user)   
69
70    def get_revision(self):
71        return Revision.objects.filter(element_type=self.element_type, element_id=self.id).order_by('-time')[0]
72
73    @classmethod
74    def get_dom_name(cls):
75        "Convert the class name to a DOM element name"
76        clsname = cls.__name__
77        return clsname[0].lower() + clsname[1:]
78
79    @staticmethod
80    def get_dom_field_name(field_name):
81        "Convert the class name to a DOM element name"
82        tokens = field_name.split('_')
83        name = tokens[0]
84        for t in tokens[1:]:
85            name += t[0].upper() + t[1:]
86        return name
87
88    def to_dom(self):
89        "Return the DOM representation of this media object"
90        impl = getDOMImplementation()
91        root = self.get_dom_name()
92        doc = impl.createDocument(None, root, None)
93        top = doc.documentElement
94        top.setAttribute("id", str(self.pk))
95        fields = self.to_dict()
96        for name, value in fields.iteritems():
97            element = doc.createElement(self.get_dom_field_name(name))
98            if isinstance(value, EnhancedModel):
99                element.setAttribute('key', str(value.pk))
100            value = unicode(value)
101            element.appendChild(doc.createTextNode(value))
102            top.appendChild(element)
103        return doc
104   
105    def to_dict(self): 
106        "Return model fields as a dict of name/value pairs"
107        fields_dict = {}
108        for field in self._meta.fields:
109            fields_dict[field.name] = getattr(self, field.name)
110        return fields_dict
111
112    def to_list(self): 
113        "Return model fields as a list"
114        fields_list = []
115        for field in self._meta.fields:
116            fields_list.append({'name': field.name, 'value': getattr(self, field.name)})
117        return fields_list
118
119    @classmethod
120    def field_label(cls, field_name):
121        try:
122            return cls._meta.get_field(field_name).verbose_name
123        except FieldDoesNotExist:
124            try:
125                return getattr(cls, field_name).verbose_name
126            except AttributeError:
127                return field_name
128
129    class Meta:
130        abstract = True
131
132class MediaResource(ModelCore):
133    "Base class of all media objects"
134
135    class Meta:
136        abstract = True
137
138class MetaCore:
139    app_label = 'telemeta'
140
141class MediaCollection(MediaResource):
142    "Describe a collection of items"
143    element_type = 'collection'
144    PUBLIC_ACCESS_CHOICES = (('none', 'none'), ('metadata', 'metadata'), ('metadata', 'full'))
145
146    published_code_regex   = 'CNRSMH_E_[0-9]{4}(?:_[0-9]{3}){2}'
147    unpublished_code_regex = 'CNRSMH_I_[0-9]{4}_[0-9]{3}'
148    code_regex             = '(?:%s|%s)' % (published_code_regex, unpublished_code_regex)
149
150    reference             = CharField(_('reference'), unique=True, null=True)
151    physical_format       = WeakForeignKey('PhysicalFormat', related_name="collections", 
152                                           verbose_name=_('archive format'))
153    old_code              = CharField(_('old code'), unique=True, null=True)
154    code                  = CharField(_('code'), unique=True, required=True)
155    title                 = CharField(_('title'), required=True)
156    alt_title             = CharField(_('original title / translation'))
157    physical_items_num    = IntegerField(_('number of components (medium / piece)'))
158    publishing_status     = WeakForeignKey('PublishingStatus', related_name="collections", 
159                                           verbose_name=_('secondary edition'))
160    creator               = CharField(_('depositor / contributor'))
161    booklet_author        = CharField(_('author of published notice'))
162    booklet_description   = TextField(_('related documentation'))
163    collector             = CharField(_('collector'))
164    collector_is_creator  = BooleanField(_('collector identical to depositor'))
165    publisher             = WeakForeignKey('Publisher', related_name="collections", 
166                                           verbose_name=_('publisher / status'))     
167    is_published          = BooleanField(_('published'))
168    year_published        = IntegerField(_('year published'))
169    publisher_collection  = WeakForeignKey('PublisherCollection', related_name="collections", 
170                                            verbose_name=_('publisher collection'))
171    publisher_serial      = CharField(_('publisher serial number'))
172    external_references   = TextField(_('bibliographic references'))
173    acquisition_mode      = WeakForeignKey('AcquisitionMode', related_name="collections", 
174                                            verbose_name=_('mode of acquisition'))
175    comment               = TextField(_('comment'))
176    metadata_author       = WeakForeignKey('MetadataAuthor', related_name="collections", 
177                                           verbose_name=_('record author'))
178    metadata_writer       = WeakForeignKey('MetadataWriter', related_name="collections", 
179                                           verbose_name=_('record writer'))
180    legal_rights          = WeakForeignKey('LegalRight', related_name="collections", 
181                                           verbose_name=_('legal rights'))
182    alt_ids               = CharField(_('copies'))
183    recorded_from_year    = IntegerField(_('recording year (from)'))
184    recorded_to_year      = IntegerField(_('recording year (until)'))
185    recording_context     = WeakForeignKey('RecordingContext', related_name="collections", 
186                                           verbose_name=_('recording context'))
187    approx_duration       = DurationField(_('approximative duration'))
188    doctype_code          = IntegerField(_('document type'))
189    travail               = CharField(_('archiver notes'))
190    state                 = TextField(_('status'))
191    cnrs_contributor      = CharField(_('CNRS depositor'))
192    items_done            = CharField(_('items finished'))
193    a_informer_07_03      = CharField(_('a_informer_07_03'))
194    ad_conversion         = WeakForeignKey('AdConversion', related_name='collections', 
195                                           verbose_name=_('A/D conversion'))
196    public_access         = CharField(_('public access'), choices=PUBLIC_ACCESS_CHOICES, 
197                                      max_length=16, default="metadata")
198
199    objects               = query.MediaCollectionManager()
200
201    def __unicode__(self):
202        return self.code
203
204    @property
205    def public_id(self):
206        return self.code
207
208    def has_mediafile(self):
209        "Tell wether this collection has any media files attached to its items"
210        items = self.items.all()
211        for item in items:
212            if item.file:
213                return True
214        return False
215
216    def __name_cmp(self, obj1, obj2):
217        return unaccent_icmp(obj1.name, obj2.name)
218
219    def countries(self):
220        "Return the countries of the items"
221        countries = []
222        items = self.items.all()
223        for item in items:
224            if item.location:
225                country = item.location.country()
226                if country and not country in countries:
227                    countries.append(country)
228
229        countries.sort(self.__name_cmp)               
230
231        return countries
232    countries.verbose_name = _("states / nations")
233
234    def ethnic_groups(self):
235        "Return the ethnic groups of the items"
236        groups = []
237        items = self.items.all()
238        for item in items:
239            if item.ethnic_group and not item.ethnic_group in groups:
240                groups.append(item.ethnic_group)
241
242        groups.sort(self.__name_cmp)               
243
244        return groups
245    ethnic_groups.verbose_name = _('populations / social groups')
246
247    def computed_duration(self):
248        duration = Duration()
249        for item in self.items.all():
250            duration += item.computed_duration()
251
252        return duration
253    computed_duration.verbose_name = _('computed duration')       
254
255    def is_valid_code(self, code):
256        "Check if the collection code is well formed"
257        if self.is_published:
258            regex = '^' + self.published_code_regex + '$'
259        else:
260            regex = '^' + self.unpublished_code_regex + '$'
261           
262        if re.match(regex, code):
263            return True
264
265        return False
266
267    def save(self, force_insert=False, force_update=False, using=None):
268        if not self.code:
269            raise RequiredFieldError(self, self._meta.get_field('code'))
270        if not self.is_valid_code(self.code):
271            raise MediaInvalidCodeError("%s is not a valid code for this collection" % self.code)
272        super(MediaCollection, self).save(force_insert, force_update, using)
273
274    class Meta(MetaCore):
275        db_table = 'media_collections'
276
277class MediaItem(MediaResource):
278    "Describe an item"
279    element_type = 'item'
280    PUBLIC_ACCESS_CHOICES = (('none', 'none'), ('metadata', 'metadata'), ('full', 'full'))
281
282    published_code_regex    = MediaCollection.published_code_regex + '(?:_[0-9]{2}){1,2}'
283    unpublished_code_regex  = MediaCollection.unpublished_code_regex + '_[0-9]{2,3}(?:_[0-9]{2}){0,2}'
284    code_regex              = '(?:%s|%s)' % (published_code_regex, unpublished_code_regex)
285
286    collection            = ForeignKey('MediaCollection', related_name="items", 
287                                       verbose_name=_('collection'))
288    track                 = CharField(_('item number'))
289    old_code              = CharField(_('old code'), unique=True, null=True)
290    code                  = CharField(_('code'), unique=True, null=True)
291    approx_duration       = DurationField(_('approximative duration'))
292    recorded_from_date    = DateField(_('recording date (from)'))
293    recorded_to_date      = DateField(_('recording date (until)'))
294    location              = WeakForeignKey('Location', related_name="items", 
295                                           db_column='location_name', verbose_name=_('location'))
296    location_comment      = CharField(_('location comment'))
297    ethnic_group          = WeakForeignKey('EthnicGroup', related_name="items", 
298                                           verbose_name=_('population / social group'))
299    title                 = CharField(_('title'), required=True)
300    alt_title             = CharField(_('original title / translation'))
301    author                = CharField(_('author'))
302    vernacular_style      = WeakForeignKey('VernacularStyle', related_name="items", 
303                                           verbose_name=_('vernacular name'))
304    context_comment       = TextField(_('comments'))
305    external_references   = TextField(_('published reference'))
306    moda_execut           = CharField(_('moda_execut'))
307    copied_from_item      = WeakForeignKey('self', related_name="copies", verbose_name=_('copy of'))
308    collector             = CharField(_('collector'))
309    cultural_area         = CharField(_('cultural area'))
310    generic_style         = WeakForeignKey('GenericStyle', related_name="items", 
311                                           verbose_name=_('generic name'))
312    collector_selection   = CharField(_('collector selection'))
313    creator_reference     = CharField(_('depositor reference'))
314    comment               = TextField(_('comment'))
315    file                  = FileField(_('file'), upload_to='items/%Y/%m/%d', db_column="filename")
316    public_access         = CharField(_('public access'), choices=PUBLIC_ACCESS_CHOICES, max_length=16, default="metadata")
317
318    objects               = query.MediaItemManager()
319
320    @property
321    def keywords(self):
322        return ContextKeyword.objects.filter(mediaitemkeyword__item = self)
323
324    @property
325    def public_id(self):
326        if self.code:
327            return self.code
328        return self.id
329
330    class Meta(MetaCore):
331        db_table = 'media_items'
332
333    def is_valid_code(self, code):
334        "Check if the item code is well formed"
335        if not re.match('^' + self.collection.code, self.code):
336            return false
337
338        if self.collection.is_published:
339            regex = '^' + self.published_code_regex + '$'
340        else:
341            regex = '^' + self.unpublished_code_regex + '$'
342
343        if re.match(regex, code):
344            return True
345
346        return False
347
348    def save(self, force_insert=False, force_update=False, using=None):
349        if not self.code:
350            raise RequiredFieldError(self, self._meta.get_field('code'))
351        if not self.is_valid_code(self.code):
352            raise MediaInvalidCodeError("%s is not a valid item code for collection %s" 
353                                        % (self.code, self.collection.code))
354        super(MediaItem, self).save(force_insert, force_update, using)
355
356    def computed_duration(self):
357        "Tell the length in seconds of this item media data"
358        # FIXME: use TimeSide?
359        seconds = 0
360        if self.file:
361            import wave
362            media = wave.open(self.file.path, "rb")
363            seconds = media.getnframes() / media.getframerate()
364            media.close()
365
366        return Duration(seconds=seconds)
367
368    computed_duration.verbose_name = _('computed duration')       
369
370    def __unicode__(self):
371        if self.code:
372            return self.code
373        return self.old_code
374
375class MediaPart(MediaResource):
376    "Describe an item part"
377    element_type = 'part'
378    item  = ForeignKey('MediaItem', related_name="parts", verbose_name=_('item'))
379    title = CharField(_('title'), required=True)
380    start = FloatField(_('start'), required=True)
381    end   = FloatField(_('end'), required=True)
382   
383    class Meta(MetaCore):
384        db_table = 'media_parts'
385
386    def __unicode__(self):
387        return self.title
388
389class Enumeration(ModelCore):
390    "Abstract enumerations base class"
391    value = CharField(_('value'), required=True, unique=True)
392   
393    def __unicode__(self):
394        return self.value
395
396    class Meta(MetaCore):
397        abstract = True
398
399class PhysicalFormat(Enumeration):
400    "Collection physical format"
401
402    class Meta(MetaCore):
403        db_table = 'physical_formats'
404
405class PublishingStatus(Enumeration):
406    "Collection publishing status"
407
408    class Meta(MetaCore):
409        db_table = 'publishing_status'
410
411class AcquisitionMode(Enumeration):
412    "Mode of acquisition of the collection"
413
414    class Meta(MetaCore):
415        db_table = 'acquisition_modes'
416
417class MetadataAuthor(Enumeration):
418    "Collection metadata author"
419
420    class Meta(MetaCore):
421        db_table = 'metadata_authors'
422
423class MetadataWriter(Enumeration): 
424    "Collection metadata writer"
425
426    class Meta(MetaCore):
427        db_table = 'metadata_writers'
428
429class LegalRight(Enumeration):
430    "Collection legal rights" 
431
432    class Meta(MetaCore):
433        db_table = 'legal_rights'
434
435class RecordingContext(Enumeration):
436    "Collection recording context"
437
438    class Meta(MetaCore):
439        db_table = 'recording_contexts'
440
441class AdConversion(Enumeration):
442    "Collection digital to analog conversion status"
443
444    class Meta(MetaCore):
445        db_table = 'ad_conversions'
446
447class VernacularStyle(Enumeration):
448    "Item vernacular style"
449
450    class Meta(MetaCore):
451        db_table = 'vernacular_styles'
452
453class GenericStyle(Enumeration):
454    "Item generic style"
455
456    class Meta(MetaCore):
457        db_table = 'generic_styles'
458
459class Instrument(ModelCore):
460    "Instrument used in the item"
461    name    = CharField(_('name'), required=True)
462
463    class Meta(MetaCore):
464        db_table = 'instruments'
465
466    def __unicode__(self):
467        return self.name
468
469class InstrumentAlias(ModelCore):
470    "Instrument other name"
471    name = CharField(_('name'), required=True)
472
473    class Meta(MetaCore):
474        db_table = 'instrument_aliases'
475
476    def __unicode__(self):
477        return self.name
478
479class InstrumentRelation(ModelCore):
480    "Instrument family"
481    instrument        = ForeignKey('Instrument', related_name="parent_relation", 
482                                   verbose_name=_('instrument'))
483    parent_instrument = ForeignKey('Instrument', related_name="child_relation", 
484                                   verbose_name=_('parent instrument'))
485
486    class Meta(MetaCore):
487        db_table = 'instrument_relations'
488        unique_together = (('instrument', 'parent_instrument'),)
489
490class InstrumentAliasRelation(ModelCore):
491    "Instrument family other name"
492    alias      = ForeignKey('InstrumentAlias', related_name="other_name", 
493                            verbose_name=_('alias'))
494    instrument = ForeignKey('InstrumentAlias', related_name="relation", 
495                            verbose_name=_('instrument'))
496
497    class Meta(MetaCore):
498        db_table = 'instrument_alias_relations'
499        unique_together = (('alias', 'instrument'),)
500
501class MediaItemPerformance(ModelCore):
502    "Item performance"
503    media_item      = ForeignKey('MediaItem', related_name="performances", 
504                                 verbose_name=_('item'))
505    instrument      = WeakForeignKey('Instrument', related_name="performances", 
506                                     verbose_name=_('instrument'))
507    alias           = WeakForeignKey('InstrumentAlias', related_name="performances", 
508                                     verbose_name=_('alias'))
509    instruments_num = CharField(_('instruments num'))
510    musicians       = CharField(_('interprets'))
511
512    class Meta(MetaCore):
513        db_table = 'media_item_performances'
514
515class User(ModelCore):
516    "Telemeta user"
517    LEVEL_CHOICES = (('user', 'user'), ('maintainer', 'maintainer'), ('admin', 'admin'))   
518
519    username   = CharField(_('username'), primary_key=True, max_length=64, required=True)
520    level      = CharField(_('level'), choices=LEVEL_CHOICES, max_length=32, required=True)
521    first_name = CharField(_('first name'))
522    last_name  = CharField(_('last name'))
523    phone      = CharField(_('phone'))
524    email      = CharField(_('email'))
525
526    class Meta(MetaCore):
527        db_table = 'users'
528
529    def __unicode__(self):
530        return self.username
531
532class Playlist(ModelCore):
533    "Item or collection playlist"
534    owner_username = ForeignKey('User', related_name="playlists", db_column="owner_username") 
535    name           = CharField(_('name'), required=True)
536
537    class Meta(MetaCore):
538        db_table = 'playlists'
539
540    def __unicode__(self):
541        return self.name
542
543class PlaylistResource(ModelCore):
544    "Playlist components"
545    RESOURCE_TYPE_CHOICES = (('item', 'item'), ('collection', 'collection'))
546
547    playlist              = ForeignKey('Playlist', related_name="resources", verbose_name=_('playlist'))
548    resource_type         = CharField(_('resource type'), choices=RESOURCE_TYPE_CHOICES, required=True)
549    resource              = IntegerField(_('resource'), required=True)
550
551    class Meta(MetaCore):
552        db_table = 'playlist_resources'
553
554class Location(ModelCore):
555    "Item location"
556    TYPE_CHOICES     = (('country', 'country'), ('continent', 'continent'), ('other', 'other'))
557
558    name             = CharField(_('name'), primary_key=True, max_length=150, required=True)
559    type             = CharField(_('type'), choices=TYPE_CHOICES, max_length=16, required=True)
560    complete_type    = ForeignKey('LocationType', related_name="types", verbose_name=_('complete type'))
561    current_name     = WeakForeignKey('self', related_name="past_names", db_column="current_name", 
562                                      verbose_name=_('current name')) 
563    is_authoritative = BooleanField(_('authoritative'))
564
565    def parent(self):
566        relations = self.parent_relations.all()
567        if relations:
568            return relations[0].parent_location
569
570        return None
571
572    def _by_type(self, typename):
573        location = self
574        while location and location.type != typename:
575            location = location.parent()
576
577        return location
578
579    def country(self):
580        return self._by_type('country')
581
582    def continent(self):
583        return self._by_type('continent')
584
585    class Meta(MetaCore):
586        db_table = 'locations'
587
588    def __unicode__(self):
589        return self.name
590
591    def sequence(self):
592        sequence = []
593        location = self
594        while location:
595            sequence.append(location)
596            location = location.parent()
597        return sequence
598
599    def fullname(self):
600       
601        return u', '.join([unicode(l) for l in self.sequence()])
602
603class LocationType(ModelCore):
604    "Location type of an item location"
605    id   = CharField(_('identifier'), max_length=64, primary_key=True, required=True)
606    name = CharField(_('name'), max_length=150, required=True)
607
608    class Meta(MetaCore):
609        db_table = 'location_types'
610
611class LocationAlias(ModelCore):
612    "Location other name"
613    location         = ForeignKey('Location', related_name="aliases", db_column="location_name", 
614                                  max_length=150, verbose_name=_('location'))
615    alias            = CharField(_('alias'), max_length=150, required=True)
616    is_authoritative = BooleanField(_('authoritative'))
617
618    def __unicode__(self):
619        return self.alias
620
621    class Meta(MetaCore):
622        db_table = 'location_aliases'
623        unique_together = (('location', 'alias'),)
624   
625class LocationRelation(ModelCore):
626    "Location family"
627    location             = ForeignKey('Location', related_name="parent_relations", 
628                                      db_column="location_name", max_length=150, verbose_name=_('location'))
629    parent_location      = ForeignKey('Location', related_name="child_relations", db_column="parent_location_name", 
630                                      null=True, max_length=150, verbose_name=_('parent location'))
631    is_authoritative     = BooleanField()
632
633    class Meta(MetaCore):
634        db_table = 'location_relations'
635   
636class ContextKeyword(Enumeration):
637    "Keyword"
638
639    class Meta(MetaCore):
640        db_table = 'context_keywords'
641
642class MediaItemKeyword(ModelCore):
643    "Item keyword"
644    item    = ForeignKey('MediaItem', verbose_name=_('item'))
645    keyword = ForeignKey('ContextKeyword', verbose_name=_('keyword'))
646
647    class Meta(MetaCore):
648        db_table = 'media_item_keywords'
649        unique_together = (('item', 'keyword'),)
650
651class Publisher(Enumeration): 
652    "Collection publisher"
653
654    class Meta(MetaCore):
655        db_table = 'publishers'
656
657class PublisherCollection(ModelCore):
658    "Collection which belongs to publisher"
659    publisher = ForeignKey('Publisher', related_name="publisher_collections", verbose_name=_('publisher'))
660    value     = CharField(_('value'), required=True)
661
662    def __unicode__(self):
663        return self.value
664
665    class Meta(MetaCore):
666        db_table = 'publisher_collections'
667
668class Revision(ModelCore):
669    "Revision made by user"
670    ELEMENT_TYPE_CHOICES = (('collection', 'collection'), ('item', 'item'), ('part', 'part'))
671    CHANGE_TYPE_CHOICES  = (('import', 'import'), ('create', 'create'), ('update', 'update'), ('delete','delete'))
672
673    element_type         = CharField(_('element type'), choices=ELEMENT_TYPE_CHOICES, max_length=16, required=True)
674    element_id           = IntegerField(_('element identifier'), required=True)
675    change_type          = CharField(_('modification type'), choices=CHANGE_TYPE_CHOICES, max_length=16, required=True)
676    time                 = DateTimeField(_('time'), auto_now_add=True)
677    user                 = ForeignKey('User', db_column='username', related_name="revisions", verbose_name=_('user'))
678   
679    @classmethod
680    def touch(cls, element, user):   
681        "Create or update a revision"
682        revision = cls(element_type=element.element_type, element_id=element.pk, 
683                       user=user, change_type='create')
684        if element.pk:
685            try: 
686                element.__class__.objects.get(pk=element.pk)
687            except ObjectDoesNotExist:
688                pass
689            else:
690                revision.change_type = 'update'
691
692        revision.save()
693        return revision
694
695    class Meta(MetaCore):
696        db_table = 'revisions'
697   
698class EthnicGroup(ModelCore):
699    "Item ethnic group"
700    name = CharField(_('name'), required=True)
701
702    class Meta(MetaCore):
703        db_table = 'ethnic_groups'
704
705    def __unicode__(self):
706        return self.name
707
708class EthnicGroupAlias(ModelCore):
709    "Item ethnic group other name" 
710    ethnic_group = ForeignKey('EthnicGroup', related_name="aliases", verbose_name=_('population / social group'))
711    name         = CharField(_('name'), required=True)
712
713    class Meta(MetaCore):
714        db_table = 'ethnic_group_aliases'
715
716
717class MissingUserError(Exception):
718    pass
719
720class RequiredFieldError(Exception):
721    def __init__(self, model, field):
722        self.model = model
723        self.field = field
724        super(Exception, self).__init__('%s.%s is required' % (model._meta.object_name, field.name))
725
726class MediaInvalidCodeError(Exception):
727    pass
Note: See TracBrowser for help on using the repository browser.