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

Revision 584, 14.9 KB (checked in by olivier, 7 months ago)

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