| 1 | # -*- coding: utf-8 -*- |
|---|
| 2 | # Copyright (C) 2007 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 | |
|---|
| 36 | from telemeta.models.core import * |
|---|
| 37 | from telemeta.util.unaccent import unaccent |
|---|
| 38 | import re |
|---|
| 39 | from django.db.models import Q |
|---|
| 40 | from django.utils.translation import ugettext_lazy as _ |
|---|
| 41 | from telemeta.models.query import * |
|---|
| 42 | from django.forms import ModelForm |
|---|
| 43 | |
|---|
| 44 | class Location(ModelCore): |
|---|
| 45 | "Locations" |
|---|
| 46 | OTHER_TYPE = 0 |
|---|
| 47 | CONTINENT = 1 |
|---|
| 48 | COUNTRY = 2 |
|---|
| 49 | TYPE_CHOICES = ((COUNTRY, _('country')), (CONTINENT, _('continent')), (OTHER_TYPE, _('other'))) |
|---|
| 50 | |
|---|
| 51 | name = CharField(_('name'), unique=True, max_length=150, required=True) |
|---|
| 52 | type = IntegerField(_('type'), choices=TYPE_CHOICES, default=OTHER_TYPE, db_index=True) |
|---|
| 53 | complete_type = ForeignKey('LocationType', related_name="locations", verbose_name=_('complete type')) |
|---|
| 54 | current_location = WeakForeignKey('self', related_name="past_names", |
|---|
| 55 | verbose_name=_('current location')) |
|---|
| 56 | latitude = FloatField(null=True) |
|---|
| 57 | longitude = FloatField(null=True) |
|---|
| 58 | is_authoritative = BooleanField(_('authoritative')) |
|---|
| 59 | |
|---|
| 60 | objects = LocationManager() |
|---|
| 61 | |
|---|
| 62 | def items(self): |
|---|
| 63 | from telemeta.models import MediaItem |
|---|
| 64 | return MediaItem.objects.by_location(self) |
|---|
| 65 | |
|---|
| 66 | def collections(self): |
|---|
| 67 | from telemeta.models import MediaCollection |
|---|
| 68 | return MediaCollection.objects.by_location(self) |
|---|
| 69 | |
|---|
| 70 | def ancestors(self, direct=False): |
|---|
| 71 | q = Q(descendant_relations__location=self) |
|---|
| 72 | if direct: |
|---|
| 73 | q &= Q(descendant_relations__is_direct=True) |
|---|
| 74 | return Location.objects.filter(q) |
|---|
| 75 | |
|---|
| 76 | def descendants(self, direct=False): |
|---|
| 77 | q = Q(ancestor_relations__ancestor_location=self) |
|---|
| 78 | if direct: |
|---|
| 79 | q &= Q(ancestor_relations__is_direct=True) |
|---|
| 80 | return Location.objects.filter(q) |
|---|
| 81 | |
|---|
| 82 | def apparented(self): |
|---|
| 83 | return Location.objects.filter( |
|---|
| 84 | Q(pk=self.id) | |
|---|
| 85 | Q(ancestor_relations__ancestor_location=self) | |
|---|
| 86 | Q(current_location=self.id)).distinct() |
|---|
| 87 | |
|---|
| 88 | def add_child(self, other): |
|---|
| 89 | LocationRelation.objects.create(location=other, ancestor_location=self, is_direct=True) |
|---|
| 90 | for location in self.ancestors(): |
|---|
| 91 | #FIXME: might raise Duplicate Entry |
|---|
| 92 | LocationRelation.objects.create(location=other, ancestor_location=location) |
|---|
| 93 | |
|---|
| 94 | def add_parent(self, other): |
|---|
| 95 | LocationRelation.objects.create(location=self, ancestor_location=other, is_direct=True) |
|---|
| 96 | for location in self.descendants(): |
|---|
| 97 | #FIXME: might raise Duplicate Entry |
|---|
| 98 | LocationRelation.objects.create(location=location, ancestor_location=other) |
|---|
| 99 | |
|---|
| 100 | def countries(self): |
|---|
| 101 | if self.type == self.COUNTRY: |
|---|
| 102 | return Location.objects.filter(pk=self.id) |
|---|
| 103 | return self.ancestors().filter(type=self.COUNTRY) |
|---|
| 104 | |
|---|
| 105 | def continents(self): |
|---|
| 106 | if self.type == self.CONTINENT: |
|---|
| 107 | return Location.objects.filter(pk=self.id) |
|---|
| 108 | return self.ancestors().filter(type=self.CONTINENT) |
|---|
| 109 | |
|---|
| 110 | class Meta(MetaCore): |
|---|
| 111 | db_table = 'locations' |
|---|
| 112 | verbose_name = _('location') |
|---|
| 113 | verbose_name_plural = _('locations') |
|---|
| 114 | ordering = ['name'] |
|---|
| 115 | |
|---|
| 116 | def __unicode__(self): |
|---|
| 117 | return self.name |
|---|
| 118 | |
|---|
| 119 | def flatname(self): |
|---|
| 120 | if self.type != self.COUNTRY and self.type != self.CONTINENT: |
|---|
| 121 | raise Exception("Flat names are only supported for countries and continents") |
|---|
| 122 | |
|---|
| 123 | map = Location.objects.flatname_map() |
|---|
| 124 | for flatname in map: |
|---|
| 125 | if self.id == map[flatname]: |
|---|
| 126 | return flatname |
|---|
| 127 | |
|---|
| 128 | return None |
|---|
| 129 | |
|---|
| 130 | def paths(self): |
|---|
| 131 | #FIXME: need to handle multiple (polyhierarchical) paths |
|---|
| 132 | path = [] |
|---|
| 133 | location = self |
|---|
| 134 | while location: |
|---|
| 135 | path.append(location) |
|---|
| 136 | try: |
|---|
| 137 | location = location.ancestors(direct=True)[0] |
|---|
| 138 | except IndexError: |
|---|
| 139 | location = None |
|---|
| 140 | return [path] |
|---|
| 141 | |
|---|
| 142 | def fullnames(self): |
|---|
| 143 | names = [] |
|---|
| 144 | for path in self.paths(): |
|---|
| 145 | names.append(u', '.join([unicode(l) for l in path])) |
|---|
| 146 | return names |
|---|
| 147 | |
|---|
| 148 | def listnames(self): |
|---|
| 149 | names = [] |
|---|
| 150 | for path in self.paths(): |
|---|
| 151 | for l in path: |
|---|
| 152 | names.append(unicode(l)) |
|---|
| 153 | return names |
|---|
| 154 | |
|---|
| 155 | class LocationType(ModelCore): |
|---|
| 156 | "Location types" |
|---|
| 157 | code = CharField(_('identifier'), max_length=64, unique=True, required=True) |
|---|
| 158 | name = CharField(_('name'), max_length=150, required=True) |
|---|
| 159 | |
|---|
| 160 | def __unicode__(self): |
|---|
| 161 | return self.name |
|---|
| 162 | |
|---|
| 163 | class Meta(MetaCore): |
|---|
| 164 | db_table = 'location_types' |
|---|
| 165 | ordering = ['name'] |
|---|
| 166 | |
|---|
| 167 | class LocationAlias(ModelCore): |
|---|
| 168 | "Location aliases" |
|---|
| 169 | location = ForeignKey('Location', related_name="aliases", verbose_name=_('location')) |
|---|
| 170 | alias = CharField(_('alias'), max_length=150, required=True) |
|---|
| 171 | is_authoritative = BooleanField(_('authoritative')) |
|---|
| 172 | |
|---|
| 173 | def __unicode__(self): |
|---|
| 174 | return self.alias |
|---|
| 175 | |
|---|
| 176 | class Meta(MetaCore): |
|---|
| 177 | db_table = 'location_aliases' |
|---|
| 178 | unique_together = (('location', 'alias'),) |
|---|
| 179 | verbose_name_plural = _('location aliases') |
|---|
| 180 | ordering = ['alias'] |
|---|
| 181 | |
|---|
| 182 | class LocationRelation(ModelCore): |
|---|
| 183 | "Location relations" |
|---|
| 184 | location = ForeignKey('Location', related_name="ancestor_relations", verbose_name=_('location')) |
|---|
| 185 | ancestor_location = ForeignKey('Location', related_name="descendant_relations", verbose_name=_('ancestor location')) |
|---|
| 186 | is_direct = BooleanField(db_index=True) |
|---|
| 187 | is_authoritative = BooleanField(_('authoritative')) |
|---|
| 188 | |
|---|
| 189 | class Meta(MetaCore): |
|---|
| 190 | db_table = 'location_relations' |
|---|
| 191 | unique_together = ('location', 'ancestor_location') |
|---|
| 192 | ordering = ['ancestor_location__name'] |
|---|
| 193 | |
|---|
| 194 | def __unicode__(self): |
|---|
| 195 | sep = ' > ' |
|---|
| 196 | if not self.is_direct: |
|---|
| 197 | sep = ' >..> ' |
|---|
| 198 | return unicode(self.ancestor_location) + sep + unicode(self.location) |
|---|
| 199 | |
|---|
| 200 | |
|---|
| 201 | class LocationForm(ModelForm): |
|---|
| 202 | class Meta: |
|---|
| 203 | model = Location |
|---|
| 204 | |
|---|
| 205 | def __init__(self, *args, **kwds): |
|---|
| 206 | super(LocationForm, self).__init__(*args, **kwds) |
|---|
| 207 | # self.fields['name'].queryset = Location.objects.order_by('name') |
|---|
| 208 | |
|---|