root/tags/telemeta-0.5.0/telemeta/models/query.py @ 581

Revision 581, 15.0 KB (checked in by olivier, 7 months ago)

merge r580 into telemeta 0.5.0 tag

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.db.models import Q, Max, Min
37from telemeta.models.core import *
38from telemeta.util.unaccent import unaccent, unaccent_icmp
39from telemeta.models.enum import EthnicGroup
40import re
41
42class MediaItemQuerySet(CoreQuerySet):
43    "Base class for all media item query sets"
44   
45    def quick_search(self, pattern):
46        "Perform a quick search on code, title and collector name"
47        pattern = pattern.strip()
48        return self.filter(
49            Q(code__contains=pattern.strip()) |
50            Q(old_code__contains=pattern.strip()) |
51            word_search_q('title', pattern) | 
52            self.by_fuzzy_collector_q(pattern)
53        )
54
55    def without_collection(self):       
56        "Find items which do not belong to any collection"
57        return self.extra(
58            where = ["collection_id NOT IN (SELECT id FROM media_collections)"]);
59
60    def by_recording_date(self, from_date, to_date = None):
61        "Find items by recording date"
62        if to_date is None:
63            return (self.filter(recorded_from_date__lte=from_date, recorded_to_date__gte=from_date))
64        else :
65            return (self.filter(Q(recorded_from_date__range=(from_date, to_date)) 
66                                | Q(recorded_to_date__range=(from_date, to_date))))
67
68    def by_title(self, pattern):
69        "Find items by title"
70        # to (sort of) sync with models.media.MediaItem.get_title()
71        return self.filter(word_search_q("title", pattern) | 
72                           (Q(title="") & word_search_q("collection__title", pattern)))
73
74    def by_publish_year(self, from_year, to_year = None):
75        "Find items by publishing year"
76        if to_year is None:
77            to_year = from_year
78        return self.filter(collection__year_published__range=(from_year, to_year)) 
79
80    def by_change_time(self, from_time = None, until_time = None):
81        "Find items by last change time" 
82        return self._by_change_time('item', from_time, until_time)
83
84    def by_location(self, location):
85        "Find items by location"
86        return self.filter(Q(location=location) | Q(location__in=location.descendants()))
87           
88    @staticmethod
89    def __name_cmp(obj1, obj2):
90        return unaccent_icmp(obj1.name, obj2.name)
91
92    def locations(self):
93        from telemeta.models import Location, LocationRelation
94        l = self.values('location')
95        r = LocationRelation.objects.filter(location__in=l).values('ancestor_location')
96        return Location.objects.filter(Q(pk__in=l) | Q(pk__in=r))
97
98    def countries(self, group_by_continent=False):
99        countries = []
100        from telemeta.models import Location
101        for id in self.filter(location__isnull=False).values_list('location', flat=True).distinct():
102            location = Location.objects.get(pk=id)
103            for l in location.countries():
104                if not l in countries:
105                    countries.append(l)
106
107        if group_by_continent:
108            grouped = {}
109
110            for country in countries:
111                for continent in country.continents():
112                    if not grouped.has_key(continent):
113                        grouped[continent] = []
114
115                    grouped[continent].append(country)
116                   
117            keys = grouped.keys()
118            keys.sort(self.__name_cmp)
119            ordered = []
120            for c in keys:
121                grouped[c].sort(self.__name_cmp)
122                ordered.append({'continent': c, 'countries': grouped[c]})
123           
124            countries = ordered
125        else:
126            countries.sort(self.__name_cmp)
127           
128        return countries                   
129
130    def virtual(self, *args):
131        qs = self
132        need_collection = False
133        related = []
134        from telemeta.models import Location
135        for f in args:
136            if f == 'apparent_collector':
137                related.append('collection')
138                qs = qs.extra(select={f: 
139                    'IF(collector_from_collection, '
140                        'IF(media_collections.collector_is_creator, '
141                           'media_collections.creator, '
142                           'media_collections.collector),'
143                        'media_items.collector)'})
144            elif f == 'country_or_continent':
145                related.append('location')
146                qs = qs.extra(select={f:
147                    'IF(locations.type = ' + str(Location.COUNTRY) + ' '
148                    'OR locations.type = ' + str(Location.CONTINENT) + ',' 
149                    'locations.name, '
150                    '(SELECT l2.name FROM location_relations AS r INNER JOIN locations AS l2 '
151                    'ON r.ancestor_location_id = l2.id '
152                    'WHERE r.location_id = media_items.location_id AND l2.type = ' + str(Location.COUNTRY) + ' LIMIT 1))'
153                })
154            else:
155                raise Exception("Unsupported virtual field: %s" % f)
156
157        if related:
158            qs = qs.select_related(*related)
159
160        return qs               
161
162    def ethnic_groups(self):
163        ids = self.filter(ethnic_group__isnull=False).values('ethnic_group');
164        return EthnicGroup.objects.filter(pk__in=ids)
165
166    @staticmethod
167    def by_fuzzy_collector_q(pattern):
168        return (word_search_q('collection__creator', pattern) | 
169                word_search_q('collection__collector', pattern) | 
170                word_search_q('collector', pattern))
171
172    def by_fuzzy_collector(self, pattern):
173        return self.filter(self.by_fuzzy_collector_q(pattern))
174
175class MediaItemManager(CoreManager):
176    "Manage media items queries"
177
178    def get_query_set(self):
179        "Return media query sets"
180        return MediaItemQuerySet(self.model)
181
182    def enriched(self):
183        "Query set with additional virtual fields such as apparent_collector and country_or_continent"
184        return self.get_query_set().virtual('apparent_collector', 'country_or_continent')
185
186    def quick_search(self, *args, **kwargs):
187        return self.get_query_set().quick_search(*args, **kwargs)
188    quick_search.__doc__ = MediaItemQuerySet.quick_search.__doc__
189
190    def without_collection(self, *args, **kwargs):
191        return self.get_query_set().without_collection(*args, **kwargs)
192    without_collection.__doc__ = MediaItemQuerySet.without_collection.__doc__   
193
194    def by_recording_date(self, *args, **kwargs):
195        return self.get_query_set().by_recording_date(*args, **kwargs)
196    by_recording_date.__doc__ = MediaItemQuerySet.by_recording_date.__doc__
197
198    def by_title(self, *args, **kwargs):
199        return self.get_query_set().by_title(*args, **kwargs)
200    by_title.__doc__ = MediaItemQuerySet.by_title.__doc__
201
202    def by_publish_year(self, *args, **kwargs):
203        return self.get_query_set().by_publish_year(*args, **kwargs)
204    by_publish_year.__doc__ = MediaItemQuerySet.by_publish_year.__doc__
205
206    def by_change_time(self, *args, **kwargs):
207        return self.get_query_set().by_change_time(*args, **kwargs)
208    by_change_time.__doc__ = MediaItemQuerySet.by_change_time.__doc__   
209
210    def by_location(self, *args, **kwargs):
211        return self.get_query_set().by_location(*args, **kwargs)
212    by_location.__doc__ = MediaItemQuerySet.by_location.__doc__   
213
214class MediaCollectionQuerySet(CoreQuerySet):
215
216    def quick_search(self, pattern):
217        "Perform a quick search on code, title and collector name"
218        pattern = pattern.strip()
219        return self.filter(
220            Q(code__contains=pattern.strip()) |
221            Q(old_code__contains=pattern.strip()) |
222            word_search_q('title', pattern) | 
223            self.by_fuzzy_collector_q(pattern)
224        )
225
226    def by_location(self, location):
227        "Find collections by location"
228        return self.filter(Q(items__location=location) | Q(items__location__in=location.descendants())).distinct()
229   
230    def by_recording_year(self, from_year, to_year=None):
231        "Find collections by recording year"
232        if to_year is None:
233            return (self.filter(recorded_from_year__lte=from_year, recorded_to_year__gte=from_year))
234        else:
235            return (self.filter(Q(recorded_from_year__range=(from_year, to_year)) | 
236                    Q(recorded_to_year__range=(from_year, to_year))))
237
238    def by_publish_year(self, from_year, to_year=None):
239        "Find collections by publishing year"
240        if to_year is None:
241            to_year = from_year
242        return self.filter(year_published__range=(from_year, to_year)) 
243
244    def by_ethnic_group(self, group):
245        "Find collections by ethnic group"
246        return self.filter(items__ethnic_group=group).distinct()
247
248    def by_change_time(self, from_time=None, until_time=None):
249        "Find collections between two dates"
250        return self._by_change_time('collection', from_time, until_time)
251
252    def virtual(self, *args):
253        qs = self
254        for f in args:
255            if f == 'apparent_collector':
256                qs = qs.extra(select={f: 'IF(media_collections.collector_is_creator, '
257                                         'media_collections.creator, media_collections.collector)'})
258            else:
259                raise Exception("Unsupported virtual field: %s" % f)
260
261        return qs               
262
263    def recording_year_range(self):
264        from_max = self.aggregate(Max('recorded_from_year'))['recorded_from_year__max']
265        to_max   = self.aggregate(Max('recorded_to_year'))['recorded_to_year__max']
266        year_max = max(from_max, to_max)
267
268        from_min = self.filter(recorded_from_year__gt=0).aggregate(Min('recorded_from_year'))['recorded_from_year__min']
269        to_min   = self.filter(recorded_to_year__gt=0).aggregate(Min('recorded_to_year'))['recorded_to_year__min']
270        year_min = min(from_min, to_min) 
271
272        if not year_max:
273            year_max = year_min
274        elif not year_min:
275            year_min = year_max
276
277        return year_min, year_max
278
279    def publishing_year_range(self):
280        year_max = self.aggregate(Max('year_published'))['year_published__max']
281        year_min = self.filter(year_published__gt=0).aggregate(Min('year_published'))['year_published__min']
282
283        return year_min, year_max
284
285    @staticmethod
286    def by_fuzzy_collector_q(pattern):
287        return word_search_q('creator', pattern) | word_search_q('collector', pattern)
288
289    def by_fuzzy_collector(self, pattern):
290        return self.filter(self.by_fuzzy_collector_q(pattern))
291
292class MediaCollectionManager(CoreManager):
293    "Manage collection queries"
294
295    def get_query_set(self):
296        "Return the collection query"
297        return MediaCollectionQuerySet(self.model)
298
299    def enriched(self):
300        "Query set with additional virtual fields such as apparent_collector"
301        return self.get_query_set().virtual('apparent_collector')
302
303    def quick_search(self, *args, **kwargs):
304        return self.get_query_set().quick_search(*args, **kwargs)
305    quick_search.__doc__ = MediaCollectionQuerySet.quick_search.__doc__
306
307    def by_location(self, *args, **kwargs):
308        return self.get_query_set().by_location(*args, **kwargs)
309    by_location.__doc__ = MediaCollectionQuerySet.by_location.__doc__
310
311    def by_recording_year(self, *args, **kwargs):
312        return self.get_query_set().by_recording_year(*args, **kwargs)
313    by_recording_year.__doc__ = MediaCollectionQuerySet.by_recording_year.__doc__
314
315    def by_publish_year(self, *args, **kwargs):
316        return self.get_query_set().by_publish_year(*args, **kwargs)
317    by_publish_year.__doc__ = MediaCollectionQuerySet.by_publish_year.__doc__
318
319    def by_ethnic_group(self, *args, **kwargs):
320        return self.get_query_set().by_ethnic_group(*args, **kwargs)
321    by_ethnic_group.__doc__ = MediaCollectionQuerySet.by_ethnic_group.__doc__
322
323    def by_change_time(self, *args, **kwargs):
324        return self.get_query_set().by_change_time(*args, **kwargs)
325    by_change_time.__doc__ = MediaCollectionQuerySet.by_change_time.__doc__
326
327    @staticmethod
328    def __name_cmp(obj1, obj2):
329        return unaccent_icmp(obj1.name, obj2.name)
330
331class LocationQuerySet(CoreQuerySet):
332    __flatname_map = None
333
334    def by_flatname(self, flatname):
335        map = self.flatname_map()
336        return self.filter(pk=map[flatname])
337
338    def flatname_map(self):
339        if self.__class__.__flatname_map:
340            return self.__class__.__flatname_map
341
342        map = {}
343        locations = self.filter(Q(type=self.model.COUNTRY) | Q(type=self.model.CONTINENT))
344        for l in locations:
345            flatname = unaccent(l.name).lower()
346            flatname = re.sub('[^a-z]', '_', flatname)
347            while map.has_key(flatname):
348                flatname = '_' + flatname
349            map[flatname] = l.id
350
351        self.__class__.__flatname_map = map
352        return map
353
354    def current(self, is_current=True):
355        if is_current:
356            where = ["locations.id = locations.current_location_id"]
357        else:
358            where = ["locations.id <> locations.current_location_id"]
359
360        return self.extra(where = where);
361
362class LocationManager(CoreManager):
363
364    def get_query_set(self):
365        "Return location query set"
366        return LocationQuerySet(self.model)
367
368    def by_flatname(self, *args, **kwargs):
369        return self.get_query_set().by_flatname(*args, **kwargs)
370    by_flatname.__doc__ = LocationQuerySet.by_flatname.__doc__   
371
372    def flatname_map(self, *args, **kwargs):
373        return self.get_query_set().flatname_map(*args, **kwargs)
374    flatname_map.__doc__ = LocationQuerySet.flatname_map.__doc__   
Note: See TracBrowser for help on using the browser.