Changeset 622588f


Ignore:
Timestamp:
Feb 10, 2010 5:42:16 PM (5 years ago)
Author:
olivier <>
Branches:
master, crem, crem2, dev, dev2, diadems, dj1.6, feature/breadcrumbs, feature/ts-0.5, feature/ts-0.5.4, feature/writecache, forma, generic, instru_search, lam, lam2, mapsv3, merge, nlivemulti, production, release/1.4.4, sabiod, security, server, social, storage, telecaster, test, video
Children:
c1a2c1c
Parents:
6a4a2b4
git-author:
olivier <> (10/02/2010 17:42:16)
git-committer:
olivier <> (10/02/2010 17:42:16)
Message:

split models

Location:
telemeta/models
Files:
4 added
2 edited
2 moved

Legend:

Unmodified
Added
Removed
  • telemeta/models/__init__.py

    re1d0274 r622588f  
    3333# Author: Olivier Guilyardi <olivier@samalyse.com> 
    3434 
    35 from telemeta.models.crem import * 
     35from telemeta.models.media import * 
     36from telemeta.models.location import * 
     37from telemeta.models.instrument import * 
     38from telemeta.models.enum import * 
     39from telemeta.models.system import * 
    3640#MediaCollection, MediaItem, MediaPart,  Revision, \ 
    3741#    PhysicalFormat, PublishingStatus 
  • telemeta/models/core.py

    r39e0100 r622588f  
    11# -*- coding: utf-8 -*- 
     2# 
    23# Copyright (C) 2007-2010 Samalyse SARL 
    3  
     4# 
    45# This software is a computer program whose purpose is to backup, analyse, 
    56# transcode and stream any audio content with its metadata over a web frontend. 
    6  
     7# 
    78# This software is governed by the CeCILL  license under French law and 
    89# abiding by the rules of distribution of free software.  You can  use, 
     
    1011# license as circulated by CEA, CNRS and INRIA at the following URL 
    1112# "http://www.cecill.info". 
    12  
     13# 
    1314# As a counterpart to the access to the source code and  rights to copy, 
    1415# modify and redistribute granted by the license, users are provided only 
     
    1617# economic rights,  and the successive licensors  have only  limited 
    1718# liability. 
    18  
     19# 
    1920# In this respect, the user's attention is drawn to the risks associated 
    2021# with loading,  using,  modifying and/or developing or reproducing the 
     
    2728# data to be ensured and,  more generally, to use and operate it in the 
    2829# same conditions as regards security. 
    29  
     30# 
    3031# The fact that you are presently reading this means that you have had 
    3132# knowledge of the CeCILL license and that you accept its terms. 
     
    3334# Authors: Olivier Guilyardi <olivier@samalyse.com> 
    3435 
     36__all__ = ['ModelCore', 'MetaCore', 'DurationField', 'Duration', 'WeakForeignKey',  
     37           'EnhancedModel', 'CharField', 'TextField', 'IntegerField', 'BooleanField',  
     38           'DateTimeField', 'FileField', 'ForeignKey', 'FloatField', 'DateField', 
     39           'RequiredFieldError', 'CoreQuerySet', 'CoreManager'] 
     40 
     41from django.core import exceptions 
     42from django import forms 
     43from xml.dom.minidom import getDOMImplementation 
     44from django.db.models.fields import FieldDoesNotExist 
     45from django.db.models import Q 
    3546from django.db import models 
    3647import datetime 
    3748from django.utils.translation import ugettext_lazy as _ 
    3849import re 
     50from django.core.exceptions import ObjectDoesNotExist 
    3951 
    4052class Duration(object): 
     
    303315        super(DateField, self).__init__(*args, **normalize_field(kwargs, '0000-00-00')) 
    304316 
     317class RequiredFieldError(Exception): 
     318    def __init__(self, model, field): 
     319        self.model = model 
     320        self.field = field 
     321        super(Exception, self).__init__('%s.%s is required' % (model._meta.object_name, field.name)) 
     322 
     323class ModelCore(EnhancedModel): 
     324 
     325    @classmethod 
     326    def required_fields(cls): 
     327        required = [] 
     328        for field in cls._meta.fields: 
     329            if not field.blank: 
     330                required.append(field) 
     331        return required 
     332 
     333    def save(self, force_insert=False, force_update=False, using=None): 
     334        required = self.required_fields() 
     335        for field in required: 
     336            if not getattr(self, field.name): 
     337                raise RequiredFieldError(self, field) 
     338        super(ModelCore, self).save(force_insert, force_update, using) 
     339 
     340    @classmethod 
     341    def get_dom_name(cls): 
     342        "Convert the class name to a DOM element name" 
     343        clsname = cls.__name__ 
     344        return clsname[0].lower() + clsname[1:] 
     345 
     346    @staticmethod 
     347    def get_dom_field_name(field_name): 
     348        "Convert the class name to a DOM element name" 
     349        tokens = field_name.split('_') 
     350        name = tokens[0] 
     351        for t in tokens[1:]: 
     352            name += t[0].upper() + t[1:] 
     353        return name 
     354 
     355    def to_dom(self): 
     356        "Return the DOM representation of this media object" 
     357        impl = getDOMImplementation() 
     358        root = self.get_dom_name() 
     359        doc = impl.createDocument(None, root, None) 
     360        top = doc.documentElement 
     361        top.setAttribute("id", str(self.pk)) 
     362        fields = self.to_dict() 
     363        for name, value in fields.iteritems(): 
     364            element = doc.createElement(self.get_dom_field_name(name)) 
     365            if isinstance(value, EnhancedModel): 
     366                element.setAttribute('key', str(value.pk)) 
     367            value = unicode(value) 
     368            element.appendChild(doc.createTextNode(value)) 
     369            top.appendChild(element) 
     370        return doc 
     371     
     372    def to_dict(self):   
     373        "Return model fields as a dict of name/value pairs" 
     374        fields_dict = {} 
     375        for field in self._meta.fields: 
     376            fields_dict[field.name] = getattr(self, field.name) 
     377        return fields_dict 
     378 
     379    def to_list(self):   
     380        "Return model fields as a list" 
     381        fields_list = [] 
     382        for field in self._meta.fields: 
     383            fields_list.append({'name': field.name, 'value': getattr(self, field.name)}) 
     384        return fields_list 
     385 
     386    @classmethod 
     387    def field_label(cls, field_name): 
     388        try: 
     389            return cls._meta.get_field(field_name).verbose_name 
     390        except FieldDoesNotExist: 
     391            try: 
     392                return getattr(cls, field_name).verbose_name 
     393            except AttributeError: 
     394                return field_name 
     395 
     396    class Meta: 
     397        abstract = True 
     398 
     399class MetaCore: 
     400    app_label = 'telemeta' 
     401 
     402class CoreQuerySet(EnhancedQuerySet): 
     403    "Base class for all query sets" 
     404 
     405    def none(self): # redundant with none() in recent Django svn 
     406        "Return an empty result set" 
     407        return self.extra(where = ["0 = 1"]) 
     408 
     409    def word_search_q(self, field, pattern): 
     410        words = re.split("[ .*-]+", pattern) 
     411        q = Q() 
     412        for w in words: 
     413            if len(w) >= 3: 
     414                kwargs = {field + '__icontains': w} 
     415                q &= Q(**kwargs) 
     416 
     417        return q 
     418 
     419    def word_search(self, field, pattern): 
     420        return self.filter(self.word_search_q(field, pattern)) 
     421         
     422    def _by_change_time(self, type, from_time = None, until_time = None): 
     423        "Search between two revision dates" 
     424        where = ["element_type = '%s'" % type] 
     425        if from_time: 
     426            where.append("time >= '%s'" % from_time.strftime('%Y-%m-%d %H:%M:%S')) 
     427        if until_time: 
     428            where.append("time <= '%s'" % until_time.strftime('%Y-%m-%d %H:%M:%S')) 
     429        return self.extra( 
     430            where = ["id IN (SELECT DISTINCT element_id FROM revisions WHERE %s)" % " AND ".join(where)]); 
     431 
     432class CoreManager(EnhancedManager): 
     433    "Base class for all models managers" 
     434 
     435    def none(self, *args, **kwargs): 
     436        "" 
     437        return self.get_query_set().none(*args, **kwargs) 
     438 
     439    def get(self, **kwargs): 
     440        if kwargs.has_key('public_id'): 
     441            try: 
     442                args = kwargs.copy() 
     443                args['code'] = kwargs['public_id'] 
     444                args.pop('public_id') 
     445                return super(CoreManager, self).get(**args) 
     446            except ObjectDoesNotExist: 
     447                args = kwargs.copy() 
     448                args['id'] = kwargs['public_id'] 
     449                args.pop('public_id') 
     450                return super(CoreManager, self).get(**args) 
     451 
     452        return super(CoreManager, self).get(**kwargs) 
     453                 
  • telemeta/models/media.py

    • Property mode changed from 100755 to 100644
    red3547b r622588f  
    3434#          David LIPSZYC <davidlipszyc@gmail.com> 
    3535 
    36 from django.core.exceptions import ObjectDoesNotExist 
    37 import cremquery as query 
    38 from xml.dom.minidom import getDOMImplementation 
     36from django.utils.translation import ugettext_lazy as _ 
     37from telemeta.models.core import * 
     38from telemeta.models.enum import ContextKeyword 
    3939from telemeta.util.unaccent import unaccent_icmp 
    4040import re 
    41 from django.db.models import FieldDoesNotExist, Q 
    42 from telemeta.models.core import DurationField, Duration, WeakForeignKey, EnhancedModel, \ 
    43                                  CharField, TextField, IntegerField, BooleanField, \ 
    44                                  DateTimeField, FileField, ForeignKey, FloatField, DateField 
    45 from telemeta.models import dublincore as dc 
    46 from django.utils.translation import ugettext_lazy as _ 
    47  
    48 class 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 
     41from telemeta.models.location import LocationRelation, Location 
     42from telemeta.models.system import Revision 
     43from telemeta.models import query 
    13144 
    13245class MediaResource(ModelCore): 
     
    14053 
    14154        return _('Private data') 
    142  
    14355    public_access_label.verbose_name = _('public access') 
     56 
     57    def save_with_revision(self, user, force_insert=False, force_update=False, using=None): 
     58        "Save a media object and add a revision" 
     59        self.save(force_insert, force_update, using) 
     60        Revision.touch(self, user)     
     61 
     62    def get_revision(self): 
     63        return Revision.objects.filter(element_type=self.element_type, element_id=self.id).order_by('-time')[0] 
    14464 
    14565    class Meta: 
    14666        abstract = True 
    147  
    148 class MetaCore: 
    149     app_label = 'telemeta' 
    15067 
    15168class MediaCollection(MediaResource): 
     
    345262        "Check if the item code is well formed" 
    346263        if not re.match('^' + self.collection.code, self.code): 
    347             return false 
     264            return False 
    348265 
    349266        if self.collection.is_published: 
     
    390307        return title 
    391308 
    392 class MediaPart(MediaResource): 
    393     "Describe an item part" 
    394     element_type = 'part' 
    395     item  = ForeignKey('MediaItem', related_name="parts", verbose_name=_('item')) 
    396     title = CharField(_('title'), required=True) 
    397     start = FloatField(_('start'), required=True) 
    398     end   = FloatField(_('end'), required=True) 
    399      
    400     class Meta(MetaCore): 
    401         db_table = 'media_parts' 
    402  
    403     def __unicode__(self): 
    404         return self.title 
    405  
    406 class Enumeration(ModelCore): 
    407     "Abstract enumerations base class" 
    408     value = CharField(_('value'), required=True, unique=True) 
    409      
    410     def __unicode__(self): 
    411         return self.value 
    412  
    413     class Meta(MetaCore): 
    414         abstract = True 
    415  
    416 class PhysicalFormat(Enumeration): 
    417     "Collection physical format" 
    418  
    419     class Meta(MetaCore): 
    420         db_table = 'physical_formats' 
    421  
    422 class PublishingStatus(Enumeration): 
    423     "Collection publishing status" 
    424  
    425     class Meta(MetaCore): 
    426         db_table = 'publishing_status' 
    427  
    428 class AcquisitionMode(Enumeration): 
    429     "Mode of acquisition of the collection" 
    430  
    431     class Meta(MetaCore): 
    432         db_table = 'acquisition_modes' 
    433  
    434 class MetadataAuthor(Enumeration): 
    435     "Collection metadata author" 
    436  
    437     class Meta(MetaCore): 
    438         db_table = 'metadata_authors' 
    439  
    440 class MetadataWriter(Enumeration):   
    441     "Collection metadata writer" 
    442  
    443     class Meta(MetaCore): 
    444         db_table = 'metadata_writers' 
    445  
    446 class LegalRight(Enumeration): 
    447     "Collection legal rights"  
    448  
    449     class Meta(MetaCore): 
    450         db_table = 'legal_rights' 
    451  
    452 class RecordingContext(Enumeration): 
    453     "Collection recording context" 
    454  
    455     class Meta(MetaCore): 
    456         db_table = 'recording_contexts' 
    457  
    458 class AdConversion(Enumeration): 
    459     "Collection digital to analog conversion status" 
    460  
    461     class Meta(MetaCore): 
    462         db_table = 'ad_conversions' 
    463  
    464 class VernacularStyle(Enumeration): 
    465     "Item vernacular style" 
    466  
    467     class Meta(MetaCore): 
    468         db_table = 'vernacular_styles' 
    469  
    470 class GenericStyle(Enumeration): 
    471     "Item generic style" 
    472  
    473     class Meta(MetaCore): 
    474         db_table = 'generic_styles' 
    475  
    476 class Instrument(ModelCore): 
    477     "Instrument used in the item" 
    478     name    = CharField(_('name'), required=True) 
    479  
    480     class Meta(MetaCore): 
    481         db_table = 'instruments' 
    482  
    483     def __unicode__(self): 
    484         return self.name 
    485  
    486 class InstrumentAlias(ModelCore): 
    487     "Instrument other name" 
    488     name = CharField(_('name'), required=True) 
    489  
    490     class Meta(MetaCore): 
    491         db_table = 'instrument_aliases' 
    492  
    493     def __unicode__(self): 
    494         return self.name 
    495  
    496 class InstrumentRelation(ModelCore): 
    497     "Instrument family" 
    498     instrument        = ForeignKey('Instrument', related_name="parent_relation",  
    499                                    verbose_name=_('instrument')) 
    500     parent_instrument = ForeignKey('Instrument', related_name="child_relation",  
    501                                    verbose_name=_('parent instrument')) 
    502  
    503     class Meta(MetaCore): 
    504         db_table = 'instrument_relations' 
    505         unique_together = (('instrument', 'parent_instrument'),) 
    506  
    507 class InstrumentAliasRelation(ModelCore): 
    508     "Instrument family other name" 
    509     alias      = ForeignKey('InstrumentAlias', related_name="other_name",  
    510                             verbose_name=_('alias')) 
    511     instrument = ForeignKey('InstrumentAlias', related_name="relation",  
    512                             verbose_name=_('instrument')) 
    513  
    514     class Meta(MetaCore): 
    515         db_table = 'instrument_alias_relations' 
    516         unique_together = (('alias', 'instrument'),) 
     309class MediaItemKeyword(ModelCore): 
     310    "Item keyword" 
     311    item    = ForeignKey('MediaItem', verbose_name=_('item'), related_name="keyword_relations") 
     312    keyword = ForeignKey('ContextKeyword', verbose_name=_('keyword'), related_name="item_relations") 
     313 
     314    class Meta(MetaCore): 
     315        db_table = 'media_item_keywords' 
     316        unique_together = (('item', 'keyword'),) 
    517317 
    518318class MediaItemPerformance(ModelCore): 
     
    530330        db_table = 'media_item_performances' 
    531331 
    532 class User(ModelCore): 
    533     "Telemeta user" 
    534     LEVEL_CHOICES = (('user', 'user'), ('maintainer', 'maintainer'), ('admin', 'admin'))     
    535  
    536     username   = CharField(_('username'), primary_key=True, max_length=64, required=True) 
    537     level      = CharField(_('level'), choices=LEVEL_CHOICES, max_length=32, required=True) 
    538     first_name = CharField(_('first name')) 
    539     last_name  = CharField(_('last name')) 
    540     phone      = CharField(_('phone')) 
    541     email      = CharField(_('email')) 
    542  
    543     class Meta(MetaCore): 
    544         db_table = 'users' 
     332class MediaPart(MediaResource): 
     333    "Describe an item part" 
     334    element_type = 'part' 
     335    item  = ForeignKey('MediaItem', related_name="parts", verbose_name=_('item')) 
     336    title = CharField(_('title'), required=True) 
     337    start = FloatField(_('start'), required=True) 
     338    end   = FloatField(_('end'), required=True) 
     339     
     340    class Meta(MetaCore): 
     341        db_table = 'media_parts' 
    545342 
    546343    def __unicode__(self): 
    547         return self.username 
     344        return self.title 
    548345 
    549346class Playlist(ModelCore): 
     
    569366        db_table = 'playlist_resources' 
    570367 
    571 class Location(ModelCore): 
    572     "Locations" 
    573     OTHER_TYPE  = 0 
    574     CONTINENT   = 1 
    575     COUNTRY     = 2 
    576     TYPE_CHOICES     = ((COUNTRY, _('country')), (CONTINENT, _('continent')), (OTHER_TYPE, _('other'))) 
    577  
    578     name             = CharField(_('name'), unique=True, max_length=150, required=True) 
    579     type             = IntegerField(_('type'), choices=TYPE_CHOICES, default=OTHER_TYPE, db_index=True) 
    580     complete_type    = ForeignKey('LocationType', related_name="locations", verbose_name=_('complete type')) 
    581     current_location = WeakForeignKey('self', related_name="past_names",  
    582                                       verbose_name=_('current location'))  
    583     latitude         = FloatField(null=True)                                     
    584     longitude        = FloatField(null=True)                                     
    585     is_authoritative = BooleanField(_('authoritative')) 
    586  
    587     objects = query.LocationManager() 
    588  
    589     def items(self): 
    590         return MediaItem.objects.by_location(self) 
    591  
    592     def collections(self): 
    593         return MediaCollection.objects.by_location(self) 
    594  
    595     def ancestors(self, direct=False): 
    596         q = Q(descendant_relations__location=self) 
    597         if direct: 
    598             q &= Q(descendant_relations__is_direct=True) 
    599         return Location.objects.filter(q)            
    600  
    601     def descendants(self, direct=False): 
    602         q = Q(ancestor_relations__ancestor_location=self) 
    603         if direct: 
    604             q &= Q(ancestor_relations__is_direct=True) 
    605         return Location.objects.filter(q)            
    606  
    607     def add_child(self, other): 
    608         LocationRelation.objects.create(location=other, ancestor_location=self, is_direct=True) 
    609         for location in self.ancestors(): 
    610             #FIXME: might raise Duplicate Entry 
    611             LocationRelation.objects.create(location=other, ancestor_location=location) 
    612              
    613     def add_parent(self, other): 
    614         LocationRelation.objects.create(location=self, ancestor_location=other, is_direct=True) 
    615         for location in self.descendants(): 
    616             #FIXME: might raise Duplicate Entry 
    617             LocationRelation.objects.create(location=location, ancestor_location=other) 
    618  
    619     def countries(self): 
    620         if self.type == self.COUNTRY: 
    621             return Location.objects.filter(pk=self.id) 
    622         return self.ancestors().filter(type=self.COUNTRY) 
    623  
    624     def continents(self): 
    625         if self.type == self.CONTINENT: 
    626             return Location.objects.filter(pk=self.id) 
    627         return self.ancestors().filter(type=self.CONTINENT) 
    628  
    629     class Meta(MetaCore): 
    630         db_table = 'locations' 
    631  
    632     def __unicode__(self): 
    633         return self.name 
    634  
    635     def flatname(self): 
    636         if self.type != self.COUNTRY and self.type != self.CONTINENT: 
    637             raise Exceptions("Flat names are only supported for countries and continents") 
    638  
    639         map = Location.objects.flatname_map() 
    640         for flatname in map: 
    641             if self.id == map[flatname]: 
    642                 return flatname 
    643  
    644         return None                     
    645  
    646     def paths(self): 
    647         #FIXME: need to handle multiple (polyhierarchical) paths 
    648         path = [] 
    649         location = self 
    650         while location: 
    651             path.append(location) 
    652             try: 
    653                 location = location.ancestors(direct=True)[0] 
    654             except IndexError: 
    655                 location = None 
    656         return [path] 
    657  
    658     def fullnames(self): 
    659         names = [] 
    660         for path in self.paths(): 
    661             names.append(u', '.join([unicode(l) for l in path])) 
    662         return names 
    663  
    664 class LocationType(ModelCore): 
    665     "Location types" 
    666     code = CharField(_('identifier'), max_length=64, unique=True, required=True) 
    667     name = CharField(_('name'), max_length=150, required=True) 
    668  
    669     class Meta(MetaCore): 
    670         db_table = 'location_types' 
    671  
    672 class LocationAlias(ModelCore): 
    673     "Location aliases" 
    674     location         = ForeignKey('Location', related_name="aliases", verbose_name=_('location')) 
    675     alias            = CharField(_('alias'), max_length=150, required=True) 
    676     is_authoritative = BooleanField(_('authoritative')) 
    677  
    678     def __unicode__(self): 
    679         return self.alias 
    680  
    681     class Meta(MetaCore): 
    682         db_table = 'location_aliases' 
    683         unique_together = (('location', 'alias'),) 
    684      
    685 class LocationRelation(ModelCore): 
    686     "Location relations" 
    687     location             = ForeignKey('Location', related_name="ancestor_relations", verbose_name=_('location')) 
    688     ancestor_location      = ForeignKey('Location', related_name="descendant_relations",  verbose_name=_('ancestor location')) 
    689     is_direct            = BooleanField(db_index=True) 
    690  
    691     class Meta(MetaCore): 
    692         db_table = 'location_relations' 
    693         unique_together = ('location', 'ancestor_location') 
    694  
    695     def __unicode__(self): 
    696         sep = ' > ' 
    697         if not self.is_direct: 
    698             sep = ' >..> '  
    699         return unicode(self.ancestor_location) + sep + unicode(self.location) 
    700      
    701 class ContextKeyword(Enumeration): 
    702     "Keyword" 
    703  
    704     class Meta(MetaCore): 
    705         db_table = 'context_keywords' 
    706  
    707 class MediaItemKeyword(ModelCore): 
    708     "Item keyword" 
    709     item    = ForeignKey('MediaItem', verbose_name=_('item'), related_name="keyword_relations") 
    710     keyword = ForeignKey('ContextKeyword', verbose_name=_('keyword'), related_name="item_relations") 
    711  
    712     class Meta(MetaCore): 
    713         db_table = 'media_item_keywords' 
    714         unique_together = (('item', 'keyword'),) 
    715  
    716 class Publisher(Enumeration):  
    717     "Collection publisher" 
    718  
    719     class Meta(MetaCore): 
    720         db_table = 'publishers' 
    721  
    722 class PublisherCollection(ModelCore): 
    723     "Collection which belongs to publisher" 
    724     publisher = ForeignKey('Publisher', related_name="publisher_collections", verbose_name=_('publisher')) 
    725     value     = CharField(_('value'), required=True) 
    726  
    727     def __unicode__(self): 
    728         return self.value 
    729  
    730     class Meta(MetaCore): 
    731         db_table = 'publisher_collections' 
    732  
    733 class Revision(ModelCore): 
    734     "Revision made by user" 
    735     ELEMENT_TYPE_CHOICES = (('collection', 'collection'), ('item', 'item'), ('part', 'part')) 
    736     CHANGE_TYPE_CHOICES  = (('import', 'import'), ('create', 'create'), ('update', 'update'), ('delete','delete')) 
    737  
    738     element_type         = CharField(_('element type'), choices=ELEMENT_TYPE_CHOICES, max_length=16, required=True) 
    739     element_id           = IntegerField(_('element identifier'), required=True) 
    740     change_type          = CharField(_('modification type'), choices=CHANGE_TYPE_CHOICES, max_length=16, required=True) 
    741     time                 = DateTimeField(_('time'), auto_now_add=True) 
    742     user                 = ForeignKey('User', db_column='username', related_name="revisions", verbose_name=_('user')) 
    743      
    744     @classmethod 
    745     def touch(cls, element, user):     
    746         "Create or update a revision" 
    747         revision = cls(element_type=element.element_type, element_id=element.pk,  
    748                        user=user, change_type='create') 
    749         if element.pk: 
    750             try:  
    751                 element.__class__.objects.get(pk=element.pk) 
    752             except ObjectDoesNotExist: 
    753                 pass 
    754             else: 
    755                 revision.change_type = 'update' 
    756  
    757         revision.save() 
    758         return revision 
    759  
    760     class Meta(MetaCore): 
    761         db_table = 'revisions' 
    762      
    763 class EthnicGroup(ModelCore): 
    764     "Item ethnic group" 
    765     name = CharField(_('name'), required=True) 
    766  
    767     class Meta(MetaCore): 
    768         db_table = 'ethnic_groups' 
    769  
    770     def __unicode__(self): 
    771         return self.name 
    772  
    773 class EthnicGroupAlias(ModelCore): 
    774     "Item ethnic group other name"  
    775     ethnic_group = ForeignKey('EthnicGroup', related_name="aliases", verbose_name=_('population / social group')) 
    776     name         = CharField(_('name'), required=True) 
    777  
    778     class Meta(MetaCore): 
    779         db_table = 'ethnic_group_aliases' 
    780  
    781  
    782 class MissingUserError(Exception): 
    783     pass 
    784  
    785 class RequiredFieldError(Exception): 
    786     def __init__(self, model, field): 
    787         self.model = model 
    788         self.field = field 
    789         super(Exception, self).__init__('%s.%s is required' % (model._meta.object_name, field.name)) 
    790  
    791368class MediaInvalidCodeError(Exception): 
    792369    pass 
     370 
  • telemeta/models/query.py

    r619d1a8 r622588f  
    11# -*- coding: utf-8 -*- 
    2 # Copyright (C) 2007 Samalyse SARL 
    3  
     2# Copyright (C) 2007-2010 Samalyse SARL 
     3# 
    44# This software is a computer program whose purpose is to backup, analyse, 
    55# transcode and stream any audio content with its metadata over a web frontend. 
    6  
     6# 
    77# This software is governed by the CeCILL  license under French law and 
    88# abiding by the rules of distribution of free software.  You can  use, 
     
    1010# license as circulated by CEA, CNRS and INRIA at the following URL 
    1111# "http://www.cecill.info". 
    12  
     12# 
    1313# As a counterpart to the access to the source code and  rights to copy, 
    1414# modify and redistribute granted by the license, users are provided only 
     
    1616# economic rights,  and the successive licensors  have only  limited 
    1717# liability. 
    18  
     18# 
    1919# In this respect, the user's attention is drawn to the risks associated 
    2020# with loading,  using,  modifying and/or developing or reproducing the 
     
    2727# data to be ensured and,  more generally, to use and operate it in the 
    2828# same conditions as regards security. 
    29  
     29# 
    3030# The fact that you are presently reading this means that you have had 
    3131# knowledge of the CeCILL license and that you accept its terms. 
     
    3434#          David LIPSZYC <davidlipszyc@gmail.com> 
    3535 
    36 from django import db 
    37 from django.db.models import Manager, Q 
    38 from telemeta.models.core import EnhancedQuerySet, EnhancedManager 
     36from django.db.models import Q 
     37from telemeta.models.core import * 
     38from telemeta.util.unaccent import unaccent, unaccent_icmp 
    3939import re 
    40 from django.core.exceptions import ObjectDoesNotExist 
    41 from django import db 
    42 import _mysql_exceptions 
    43 from telemeta.util.unaccent import unaccent_icmp, unaccent 
    44  
    45 class CoreQuerySet(EnhancedQuerySet): 
    46     "Base class for all query sets" 
    47  
    48     def none(self): # redundant with none() in recent Django svn 
    49         "Return an empty result set" 
    50         return self.extra(where = ["0 = 1"]) 
    51  
    52     def word_search_q(self, field, pattern): 
    53         words = re.split("[ .*-]+", pattern) 
    54         q = Q() 
    55         for w in words: 
    56             if len(w) >= 3: 
    57                 kwargs = {field + '__icontains': w} 
    58                 q &= Q(**kwargs) 
    59  
    60         return q 
    61  
    62     def word_search(self, field, pattern): 
    63         return self.filter(self.word_search_q(field, pattern)) 
    64          
    65     def _by_change_time(self, type, from_time = None, until_time = None): 
    66         "Search between two revision dates" 
    67         where = ["element_type = '%s'" % type] 
    68         if from_time: 
    69             where.append("time >= '%s'" % from_time.strftime('%Y-%m-%d %H:%M:%S')) 
    70         if until_time: 
    71             where.append("time <= '%s'" % until_time.strftime('%Y-%m-%d %H:%M:%S')) 
     40 
     41class MediaItemQuerySet(CoreQuerySet): 
     42    "Base class for all media item query sets" 
     43     
     44    def quick_search(self, pattern): 
     45        "Perform a quick search on id and title" 
     46        return self.filter( 
     47            self.word_search_q('id', pattern) | 
     48            self.word_search_q('title', pattern) |   
     49            self.word_search_q('author', pattern)    
     50        ) 
     51 
     52    def without_collection(self):         
     53        "Find items which do not belong to any collection" 
    7254        return self.extra( 
    73             where = ["id IN (SELECT DISTINCT element_id FROM revisions WHERE %s)" % " AND ".join(where)]); 
    74  
    75 class CoreManager(EnhancedManager): 
    76     "Base class for all models managers" 
    77  
    78     def none(self, *args, **kwargs): 
    79         "" 
    80         return self.get_query_set().none(*args, **kwargs) 
    81  
    82     def get(self, **kwargs): 
    83         if kwargs.has_key('public_id'): 
    84             try: 
    85                 args = kwargs.copy() 
    86                 args['code'] = kwargs['public_id'] 
    87                 args.pop('public_id') 
    88                 return super(CoreManager, self).get(**args) 
    89             except ObjectDoesNotExist: 
    90                 args = kwargs.copy() 
    91                 args['id'] = kwargs['public_id'] 
    92                 args.pop('public_id') 
    93                 return super(CoreManager, self).get(**args) 
    94  
    95         return super(CoreManager, self).get(**kwargs) 
    96                  
     55            where = ["collection_id NOT IN (SELECT id FROM media_collections)"]); 
     56 
     57    def by_recording_date(self, from_date, to_date = None): 
     58        "Find items by recording date" 
     59        if to_date is None: 
     60            return (self.filter(recorded_from_date__lte=from_date, recorded_to_date__gte=from_date)) 
     61        else : 
     62            return (self.filter(Q(recorded_from_date__range=(from_date, to_date))  
     63                                | Q(recorded_to_date__range=(from_date, to_date)))) 
     64 
     65    def by_title(self, pattern): 
     66        "Find items by title" 
     67        # to (sort of) sync with models.media.MediaItem.get_title() 
     68        return self.filter(self.word_search_q("title", pattern) | self.word_search_q("collection__title", pattern)) 
     69 
     70    def by_publish_year(self, from_year, to_year = None): 
     71        "Find items by publishing year" 
     72        if to_year is None: 
     73            to_year = from_year 
     74        return self.filter(collection__year_published__range=(from_year, to_year))  
     75 
     76    def by_change_time(self, from_time = None, until_time = None): 
     77        "Find items by last change time"   
     78        return self._by_change_time('item', from_time, until_time) 
     79 
     80    def by_location(self, location): 
     81        "Find items by location" 
     82        from telemeta.models import LocationRelation 
     83        descendants = LocationRelation.objects.filter(ancestor_location=location) 
     84        return self.filter(Q(location=location) | Q(location__in=descendants)) 
     85            
     86    @staticmethod 
     87    def __name_cmp(obj1, obj2): 
     88        return unaccent_icmp(obj1.name, obj2.name) 
     89 
     90    def countries(self, group_by_continent=False): 
     91        countries = [] 
     92        from telemeta.models import Location 
     93        for id in self.filter(location__isnull=False).values_list('location', flat=True).distinct(): 
     94            location = Location.objects.get(pk=id) 
     95            for l in location.countries(): 
     96                if not l in countries: 
     97                    countries.append(l) 
     98 
     99        if group_by_continent: 
     100            grouped = {} 
     101 
     102            for country in countries: 
     103                for continent in country.continents(): 
     104                    if not grouped.has_key(continent): 
     105                        grouped[continent] = [] 
     106 
     107                    grouped[continent].append(country) 
     108                     
     109            keys = grouped.keys() 
     110            keys.sort(self.__name_cmp) 
     111            ordered = [] 
     112            for c in keys: 
     113                grouped[c].sort(self.__name_cmp) 
     114                ordered.append({'continent': c, 'countries': grouped[c]}) 
     115             
     116            countries = ordered 
     117             
     118        return countries                     
     119 
     120    def virtual(self, *args): 
     121        qs = self 
     122        need_collection = False 
     123        related = [] 
     124        from telemeta.models import Location 
     125        for f in args: 
     126            if f == 'apparent_collector': 
     127                related.append('collection') 
     128                qs = qs.extra(select={f:  
     129                    'IF(collector_from_collection, ' 
     130                        'IF(media_collections.collector_is_creator, ' 
     131                           'media_collections.creator, ' 
     132                           'media_collections.collector),' 
     133                        'media_items.collector)'}) 
     134            elif f == 'country_or_continent': 
     135                related.append('location') 
     136                qs = qs.extra(select={f: 
     137                    'IF(locations.type = ' + str(Location.COUNTRY) + ' ' 
     138                    'OR locations.type = ' + str(Location.CONTINENT) + ','  
     139                    'locations.name, ' 
     140                    '(SELECT l2.name FROM location_relations AS r INNER JOIN locations AS l2 ' 
     141                    'ON r.ancestor_location_id = l2.id ' 
     142                    'WHERE r.location_id = media_items.location_id AND l2.type = ' + str(Location.COUNTRY) + ' ))' 
     143                }) 
     144            else: 
     145                raise Exception("Unsupported virtual field: %s" % f) 
     146 
     147        if related: 
     148            qs = qs.select_related(*related) 
     149 
     150        return qs                 
     151 
     152    def ethnic_groups(self): 
     153        return self.filter(ethnic_group__isnull=False) \ 
     154               .values_list('ethnic_group__name', flat=True) \ 
     155               .distinct().order_by('ethnic_group__name')         
     156 
     157class MediaItemManager(CoreManager): 
     158    "Manage media items queries" 
     159 
     160    def get_query_set(self): 
     161        "Return media query sets" 
     162        return MediaItemQuerySet(self.model) 
     163 
     164    def enriched(self): 
     165        "Query set with additional virtual fields such as apparent_collector and country_or_continent" 
     166        return self.get_query_set().virtual('apparent_collector', 'country_or_continent') 
     167 
     168    def quick_search(self, *args, **kwargs): 
     169        return self.get_query_set().quick_search(*args, **kwargs) 
     170    quick_search.__doc__ = MediaItemQuerySet.quick_search.__doc__ 
     171 
     172    def without_collection(self, *args, **kwargs): 
     173        return self.get_query_set().without_collection(*args, **kwargs) 
     174    without_collection.__doc__ = MediaItemQuerySet.without_collection.__doc__    
     175 
     176    def by_recording_date(self, *args, **kwargs): 
     177        return self.get_query_set().by_recording_date(*args, **kwargs) 
     178    by_recording_date.__doc__ = MediaItemQuerySet.by_recording_date.__doc__ 
     179 
     180    def by_title(self, *args, **kwargs): 
     181        return self.get_query_set().by_title(*args, **kwargs) 
     182    by_title.__doc__ = MediaItemQuerySet.by_title.__doc__ 
     183 
     184    def by_publish_year(self, *args, **kwargs): 
     185        return self.get_query_set().by_publish_year(*args, **kwargs) 
     186    by_publish_year.__doc__ = MediaItemQuerySet.by_publish_year.__doc__ 
     187 
     188    def by_change_time(self, *args, **kwargs): 
     189        return self.get_query_set().by_change_time(*args, **kwargs) 
     190    by_change_time.__doc__ = MediaItemQuerySet.by_change_time.__doc__     
     191 
     192    def by_location(self, *args, **kwargs): 
     193        return self.get_query_set().by_location(*args, **kwargs) 
     194    by_location.__doc__ = MediaItemQuerySet.by_location.__doc__     
     195 
    97196class MediaCollectionQuerySet(CoreQuerySet): 
    98197 
     
    183282    def stat_continents(self, only_continent=None):       
    184283        "Return the number of collections by continents and countries as a tree" 
    185         from telemeta.models import MediaItem, Location 
    186284 
    187285        countries = [] 
     
    214312        return ordered 
    215313 
    216  
    217 class MediaItemQuerySet(CoreQuerySet): 
    218     "Base class for all media item query sets" 
    219      
    220     def quick_search(self, pattern): 
    221         "Perform a quick search on id and title" 
    222         return self.filter( 
    223             self.word_search_q('id', pattern) | 
    224             self.word_search_q('title', pattern) |   
    225             self.word_search_q('author', pattern)    
    226         ) 
    227  
    228     def without_collection(self):         
    229         "Find items which do not belong to any collection" 
    230         return self.extra( 
    231             where = ["collection_id NOT IN (SELECT id FROM media_collections)"]); 
    232  
    233     def by_recording_date(self, from_date, to_date = None): 
    234         "Find items by recording date" 
    235         if to_date is None: 
    236             return (self.filter(recorded_from_date__lte=from_date, recorded_to_date__gte=from_date)) 
    237         else : 
    238             return (self.filter(Q(recorded_from_date__range=(from_date, to_date))  
    239                                 | Q(recorded_to_date__range=(from_date, to_date)))) 
    240  
    241     def by_title(self, pattern): 
    242         "Find items by title" 
    243         # to (sort of) sync with models.media.MediaItem.get_title() 
    244         return self.filter(self.word_search_q("title", pattern) | self.word_search_q("collection__title", pattern)) 
    245  
    246     def by_publish_year(self, from_year, to_year = None): 
    247         "Find items by publishing year" 
    248         if to_year is None: 
    249             to_year = from_year 
    250         return self.filter(collection__year_published__range=(from_year, to_year))  
    251  
    252     def by_change_time(self, from_time = None, until_time = None): 
    253         "Find items by last change time"   
    254         return self._by_change_time('item', from_time, until_time) 
    255  
    256     def by_location(self, location): 
    257         "Find items by location" 
    258         from telemeta.models import LocationRelation 
    259         descendants = LocationRelation.objects.filter(ancestor_location=location) 
    260         return self.filter(Q(location=location) | Q(location__in=descendants)) 
    261             
    262     @staticmethod 
    263     def __name_cmp(obj1, obj2): 
    264         return unaccent_icmp(obj1.name, obj2.name) 
    265  
    266     def countries(self, group_by_continent=False): 
    267         from telemeta.models import Location 
    268         countries = [] 
    269         for id in self.filter(location__isnull=False).values_list('location', flat=True).distinct(): 
    270             location = Location.objects.get(pk=id) 
    271             for l in location.countries(): 
    272                 if not l in countries: 
    273                     countries.append(l) 
    274  
    275         if group_by_continent: 
    276             grouped = {} 
    277  
    278             for country in countries: 
    279                 for continent in country.continents(): 
    280                     if not grouped.has_key(continent): 
    281                         grouped[continent] = [] 
    282  
    283                     grouped[continent].append(country) 
    284                      
    285             keys = grouped.keys() 
    286             keys.sort(self.__name_cmp) 
    287             ordered = [] 
    288             for c in keys: 
    289                 grouped[c].sort(self.__name_cmp) 
    290                 ordered.append({'continent': c, 'countries': grouped[c]}) 
    291              
    292             countries = ordered 
    293              
    294         return countries                     
    295  
    296     def virtual(self, *args): 
    297         qs = self 
    298         need_collection = False 
    299         related = [] 
    300         for f in args: 
    301             if f == 'apparent_collector': 
    302                 related.append('collection') 
    303                 qs = qs.extra(select={f:  
    304                     'IF(collector_from_collection, ' 
    305                         'IF(media_collections.collector_is_creator, ' 
    306                            'media_collections.creator, ' 
    307                            'media_collections.collector),' 
    308                         'media_items.collector)'}) 
    309             elif f == 'country_or_continent': 
    310                 from telemeta.models import Location 
    311                 related.append('location') 
    312                 qs = qs.extra(select={f: 
    313                     'IF(locations.type = ' + str(Location.COUNTRY) + ' ' 
    314                     'OR locations.type = ' + str(Location.CONTINENT) + ','  
    315                     'locations.name, ' 
    316                     '(SELECT l2.name FROM location_relations AS r INNER JOIN locations AS l2 ' 
    317                     'ON r.ancestor_location_id = l2.id ' 
    318                     'WHERE r.location_id = media_items.location_id AND l2.type = ' + str(Location.COUNTRY) + ' ))' 
    319                 }) 
    320             else: 
    321                 raise Exception("Unsupported virtual field: %s" % f) 
    322  
    323         if related: 
    324             qs = qs.select_related(*related) 
    325  
    326         return qs                 
    327  
    328     def ethnic_groups(self): 
    329         return self.filter(ethnic_group__isnull=False) \ 
    330                .values_list('ethnic_group__name', flat=True) \ 
    331                .distinct().order_by('ethnic_group__name')         
    332  
    333 class MediaItemManager(CoreManager): 
    334     "Manage media items queries" 
    335  
    336     def get_query_set(self): 
    337         "Return media query sets" 
    338         return MediaItemQuerySet(self.model) 
    339  
    340     def enriched(self): 
    341         "Query set with additional virtual fields such as apparent_collector and country_or_continent" 
    342         return self.get_query_set().virtual('apparent_collector', 'country_or_continent') 
    343  
    344     def quick_search(self, *args, **kwargs): 
    345         return self.get_query_set().quick_search(*args, **kwargs) 
    346     quick_search.__doc__ = MediaItemQuerySet.quick_search.__doc__ 
    347  
    348     def without_collection(self, *args, **kwargs): 
    349         return self.get_query_set().without_collection(*args, **kwargs) 
    350     without_collection.__doc__ = MediaItemQuerySet.without_collection.__doc__    
    351  
    352     def by_recording_date(self, *args, **kwargs): 
    353         return self.get_query_set().by_recording_date(*args, **kwargs) 
    354     by_recording_date.__doc__ = MediaItemQuerySet.by_recording_date.__doc__ 
    355  
    356     def by_title(self, *args, **kwargs): 
    357         return self.get_query_set().by_title(*args, **kwargs) 
    358     by_title.__doc__ = MediaItemQuerySet.by_title.__doc__ 
    359  
    360     def by_publish_year(self, *args, **kwargs): 
    361         return self.get_query_set().by_publish_year(*args, **kwargs) 
    362     by_publish_year.__doc__ = MediaItemQuerySet.by_publish_year.__doc__ 
    363  
    364     def by_change_time(self, *args, **kwargs): 
    365         return self.get_query_set().by_change_time(*args, **kwargs) 
    366     by_change_time.__doc__ = MediaItemQuerySet.by_change_time.__doc__     
    367  
    368     def by_location(self, *args, **kwargs): 
    369         return self.get_query_set().by_location(*args, **kwargs) 
    370     by_location.__doc__ = MediaItemQuerySet.by_location.__doc__     
    371  
    372314class LocationQuerySet(CoreQuerySet): 
     315    __flatname_map = None 
     316 
    373317    def by_flatname(self, flatname): 
    374         map = LocationManager.flatname_map() 
     318        map = self.flatname_map() 
    375319        return self.filter(pk=map[flatname]) 
    376320 
    377 class LocationManager(CoreManager): 
    378     __flatname_map = None 
    379  
    380     def get_query_set(self): 
    381         "Return location query set" 
    382         return LocationQuerySet(self.model) 
    383  
    384     @classmethod 
    385     def flatname_map(cls): 
    386         if cls.__flatname_map: 
    387             return cls.__flatname_map 
    388  
    389         from telemeta.models import Location 
     321    def flatname_map(self): 
     322        if self.__class__.__flatname_map: 
     323            return self.__class__.__flatname_map 
     324 
    390325        map = {} 
    391         locations = Location.objects.filter(Q(type=Location.COUNTRY) | Q(type=Location.CONTINENT)) 
     326        locations = self.filter(Q(type=self.model.COUNTRY) | Q(type=self.model.CONTINENT)) 
    392327        for l in locations: 
    393328            flatname = unaccent(l.name).lower() 
     
    397332            map[flatname] = l.id 
    398333 
    399         cls.__flatname_map = map 
     334        self.__class__.__flatname_map = map 
    400335        return map 
    401              
     336 
     337class LocationManager(CoreManager): 
     338 
     339    def get_query_set(self): 
     340        "Return location query set" 
     341        return LocationQuerySet(self.model) 
     342 
    402343    def by_flatname(self, *args, **kwargs): 
    403344        return self.get_query_set().by_flatname(*args, **kwargs) 
    404345    by_flatname.__doc__ = LocationQuerySet.by_flatname.__doc__     
    405346 
    406      
     347    def flatname_map(self, *args, **kwargs): 
     348        return self.get_query_set().flatname_map(*args, **kwargs) 
     349    flatname_map.__doc__ = LocationQuerySet.flatname_map.__doc__     
     350 
Note: See TracChangeset for help on using the changeset viewer.