]> git.parisson.com Git - telemeta.git/commitdiff
improve and optimize location models structure and querying
authorolivier <>
Mon, 1 Feb 2010 18:11:39 +0000 (18:11 +0000)
committerolivier <>
Mon, 1 Feb 2010 18:11:39 +0000 (18:11 +0000)
telemeta/models/__init__.py
telemeta/models/crem.py
telemeta/models/cremquery.py
telemeta/templates/telemeta_default/geo_continents.html
telemeta/templates/telemeta_default/geo_countries.html
telemeta/templates/telemeta_default/geo_country_collections.html
telemeta/web/base.py

index 09106a8b88cf074e7701698b3e079d481c63c756..3b4367b06c698b12df5842f28c727629716904f6 100644 (file)
@@ -36,43 +36,3 @@ from telemeta.models.crem import *
 #MediaCollection, MediaItem, MediaPart,  Revision, \
 #    PhysicalFormat, PublishingStatus
 
-from django.db.models.signals import post_syncdb
-
-def syncdb_callback(sender, **kwargs):
-    from django.db import connection
-    import _mysql_exceptions
-    cursor = connection.cursor()
-    print "Creating MySQL stored procedure"
-    try:
-        cursor.execute("DROP FUNCTION IF EXISTS telemeta_location_ascendant")
-    except _mysql_exceptions.Warning:
-        pass
-    try:
-        cursor.execute("""
-        CREATE FUNCTION telemeta_location_ascendant(loc CHAR(150), asc_type CHAR(16))
-          RETURNS CHAR(150) 
-          READS SQL DATA 
-          BEGIN 
-            DECLARE t, n CHAR(150); 
-            DECLARE c INT;
-            SELECT COUNT(*) INTO c FROM locations WHERE name = loc;
-            IF c = 0 THEN
-              RETURN NULL;
-            END IF;
-            SELECT name, type INTO n, t FROM locations WHERE name = loc;
-            WHILE t <> asc_type DO
-              SELECT COUNT(*) INTO c FROM location_relations WHERE location_name = n;
-              IF c = 0 THEN
-                RETURN NULL;
-              END IF;
-              SELECT parent_location_name INTO n FROM location_relations WHERE location_name = n LIMIT 1;
-              SELECT type INTO t FROM locations WHERE name = n;
-            END WHILE;  
-            RETURN n;
-          END""")
-    except _mysql_exceptions.Warning:
-        pass
-
-post_syncdb.connect(syncdb_callback)    
-    
-
index d56181339a12c8f02f823039a692f5213d8e1b74..4797c6927510441ed3785b3c0f335d8c47459de2 100755 (executable)
@@ -219,13 +219,11 @@ class MediaCollection(MediaResource):
     def countries(self):
         "Return the countries of the items"
         countries = []
-        items = self.items.all()
-        for item in items:
-            if item.location:
-                country = item.location.country()
-                if country and not country in countries:
+        for item in self.items.filter(location__isnull=False):
+            for country in item.location.countries():
+                if not country in countries:
                     countries.append(country)
-
+            
         countries.sort(self.__name_cmp)                
 
         return countries
@@ -291,8 +289,7 @@ class MediaItem(MediaResource):
     approx_duration       = DurationField(_('approximative duration'))
     recorded_from_date    = DateField(_('recording date (from)'))
     recorded_to_date      = DateField(_('recording date (until)'))
-    location              = WeakForeignKey('Location', related_name="items", 
-                                           db_column='location_name', verbose_name=_('location'))
+    location              = WeakForeignKey('Location', verbose_name=_('location'))
     location_comment      = CharField(_('location comment'))
     ethnic_group          = WeakForeignKey('EthnicGroup', related_name="items", 
                                            verbose_name=_('population / social group'))
@@ -552,35 +549,37 @@ class PlaylistResource(ModelCore):
         db_table = 'playlist_resources'
 
 class Location(ModelCore):
-    "Item location"
-    TYPE_CHOICES     = (('country', 'country'), ('continent', 'continent'), ('other', 'other'))
-
-    name             = CharField(_('name'), primary_key=True, max_length=150, required=True)
-    type             = CharField(_('type'), choices=TYPE_CHOICES, max_length=16, required=True)
-    complete_type    = ForeignKey('LocationType', related_name="types", verbose_name=_('complete type'))
-    current_name     = WeakForeignKey('self', related_name="past_names", db_column="current_name", 
-                                      verbose_name=_('current name')) 
+    "Locations"
+    OTHER_TYPE  = 0
+    CONTINENT   = 1
+    COUNTRY     = 2
+    TYPE_CHOICES     = ((COUNTRY, _('country')), (CONTINENT, _('continent')), (OTHER_TYPE, _('other')))
+
+    name             = CharField(_('name'), unique=True, max_length=150, required=True)
+    type             = IntegerField(_('type'), choices=TYPE_CHOICES, required=True, db_index=True)
+    complete_type    = ForeignKey('LocationType', related_name="locations", verbose_name=_('complete type'))
+    current_location = WeakForeignKey('self', related_name="past_names", 
+                                      verbose_name=_('current location')) 
     is_authoritative = BooleanField(_('authoritative'))
 
-    def parent(self):
-        relations = self.parent_relations.all()
-        if relations:
-            return relations[0].parent_location
+    objects = query.LocationManager()
 
-        return None
+    def items(self):
+        return MediaItem.objects.by_location(self)
 
-    def _by_type(self, typename):
-        location = self
-        while location and location.type != typename:
-            location = location.parent()
+    def collections(self):
+        return MediaCollection.objects.by_location(self)
 
-        return location
+    def ancestors(self):
+        return Location.objects.filter(descendant_relations__location=self)
 
-    def country(self):
-        return self._by_type('country')
+    def descendants(self):
+        return Location.objects.filter(ancestor_relations__ancestor_location=self)
 
-    def continent(self):
-        return self._by_type('continent')
+    def countries(self):
+        if self.type == self.COUNTRY:
+            return Location.objects.filter(pk=self.id)
+        return self.ancestors().filter(type=self.COUNTRY)
 
     class Meta(MetaCore):
         db_table = 'locations'
@@ -588,7 +587,18 @@ class Location(ModelCore):
     def __unicode__(self):
         return self.name
 
-    def sequence(self):
+    def flatname(self):
+        if self.type != self.COUNTRY and self.type != self.CONTINENT:
+            raise Exceptions("Flat names are only supported for countries and continents")
+
+        map = Location.objects.flatname_map()
+        for flatname in map:
+            if self.id == map[flatname]:
+                return flatname
+
+        return None                    
+
+    def sequences(self):
         sequence = []
         location = self
         while location:
@@ -596,22 +606,21 @@ class Location(ModelCore):
             location = location.parent()
         return sequence
 
-    def fullname(self):
+    def fullnames(self):
         
         return u', '.join([unicode(l) for l in self.sequence()])
 
 class LocationType(ModelCore):
-    "Location type of an item location"
-    id   = CharField(_('identifier'), max_length=64, primary_key=True, required=True)
+    "Location types"
+    code = CharField(_('identifier'), max_length=64, unique=True, required=True)
     name = CharField(_('name'), max_length=150, required=True)
 
     class Meta(MetaCore):
         db_table = 'location_types'
 
 class LocationAlias(ModelCore):
-    "Location other name"
-    location         = ForeignKey('Location', related_name="aliases", db_column="location_name", 
-                                  max_length=150, verbose_name=_('location'))
+    "Location aliases"
+    location         = ForeignKey('Location', related_name="aliases", verbose_name=_('location'))
     alias            = CharField(_('alias'), max_length=150, required=True)
     is_authoritative = BooleanField(_('authoritative'))
 
@@ -623,15 +632,14 @@ class LocationAlias(ModelCore):
         unique_together = (('location', 'alias'),)
     
 class LocationRelation(ModelCore):
-    "Location family"
-    location             = ForeignKey('Location', related_name="parent_relations", 
-                                      db_column="location_name", max_length=150, verbose_name=_('location'))
-    parent_location      = ForeignKey('Location', related_name="child_relations", db_column="parent_location_name", 
-                                      null=True, max_length=150, verbose_name=_('parent location'))
-    is_authoritative     = BooleanField()
+    "Location relations"
+    location             = ForeignKey('Location', related_name="ancestor_relations", verbose_name=_('location'))
+    ancestor_location      = ForeignKey('Location', related_name="descendant_relations",  verbose_name=_('ancestor location'))
+    is_direct            = BooleanField(db_index=True)
 
     class Meta(MetaCore):
         db_table = 'location_relations'
+        unique_together = ('location', 'ancestor_location')
     
 class ContextKeyword(Enumeration):
     "Keyword"
index b4452ba9dbc7e1443b2caac4980256d1b7654f7f..fb8f0484419f28cb6616a89823af6a8a58609427 100644 (file)
@@ -40,6 +40,7 @@ import re
 from django.core.exceptions import ObjectDoesNotExist
 from django import db
 import _mysql_exceptions
+from telemeta.util.unaccent import unaccent_icmp, unaccent
 
 class CoreQuerySet(EnhancedQuerySet):
     "Base class for all query sets"
@@ -103,13 +104,9 @@ class MediaCollectionQuerySet(CoreQuerySet):
             self.word_search_q('creator', pattern)   
         )
 
-    def by_country(self, country):
+    def by_location(self, location):
         "Find collections by country"
-        db.connection.cursor() # Need this to establish connection
-        country = db.connection.connection.literal(country)
-        return self.extra(where=["media_items.collection_id = media_collections.id",
-                                 "telemeta_location_ascendant(media_items.location_name, 'country') = %s" % country],
-                          tables=['media_items']).distinct()
+        return self.filter(Q(items__location=location) | Q(items__location__in=location.descendants())).distinct()
     
     def by_continent(self, continent):
         "Find collections by continent"
@@ -148,9 +145,9 @@ class MediaCollectionManager(CoreManager):
         return self.get_query_set().quick_search(*args, **kwargs)
     quick_search.__doc__ = MediaCollectionQuerySet.quick_search.__doc__
 
-    def by_country(self, *args, **kwargs):
-        return self.get_query_set().by_country(*args, **kwargs)
-    by_country.__doc__ = MediaCollectionQuerySet.by_country.__doc__
+    def by_location(self, *args, **kwargs):
+        return self.get_query_set().by_location(*args, **kwargs)
+    by_location.__doc__ = MediaCollectionQuerySet.by_location.__doc__
 
     def by_continent(self, *args, **kwargs):
         return self.get_query_set().by_continent(*args, **kwargs)
@@ -172,63 +169,42 @@ class MediaCollectionManager(CoreManager):
         return self.get_query_set().by_change_time(*args, **kwargs)
     by_change_time.__doc__ = MediaCollectionQuerySet.by_change_time.__doc__
 
-    def stat_continents(self, order_by='nitems'):      
+    @staticmethod
+    def __name_cmp(obj1, obj2):
+        return unaccent_icmp(obj1.name, obj2.name)
+
+    def stat_continents(self, only_continent=None):      
         "Return the number of collections by continents and countries as a tree"
-        from django.db import connection
-        cursor = connection.cursor()
-        if order_by == 'nitems':
-            order_by = 'items_num DESC'
-        elif order_by != 'country':
-            raise Exception("stat_continents() can only order by nitems or country")
-
-        try:
-            cursor.execute("""
-                SELECT telemeta_location_ascendant(location_name, 'continent') as continent, 
-                       telemeta_location_ascendant(location_name, 'country') as country, 
-                       count(*) AS items_num 
-                FROM media_collections INNER JOIN media_items 
-                ON media_collections.id = media_items.collection_id 
-                GROUP BY country ORDER BY continent, """ + order_by)
-        except _mysql_exceptions.Warning:
-            pass
-        result_set = cursor.fetchall()
+        from telemeta.models import MediaItem, Location
+
+        countries = []
+        for lid in MediaItem.objects.filter(location__isnull=False).values_list('location', flat=True).distinct():
+            location = Location.objects.get(pk=lid)
+            if not only_continent or (only_continent in location.ancestors().filter(type=Location.CONTINENT)):
+                for l in location.countries():
+                    if not l in countries:
+                        countries.append(l)
+                
         stat = {}
-        for continent, country, count in result_set:
-            if continent and country:
-                if stat.has_key(continent):
-                    stat[continent].append({'name':country, 'count':count})
-                else:
-                    stat[continent] = [{'name':country, 'count':count}]
-
-        keys = stat.keys()
-        keys.sort()
-        ordered = [{'name': k, 'countries': stat[k]} for k in keys]
-        return ordered
-
-    def list_countries(self):
-        "Return a 2D list of all countries with continents"
 
-        from django.db import connection
-        cursor = connection.cursor()
+        for country in countries:
+            count = country.collections().count()
+            for continent in country.ancestors().filter(type=Location.CONTINENT):
+                if not stat.has_key(continent):
+                    stat[continent] = {}
 
-        cursor.execute("SELECT continent, etat FROM media_items "
-            "GROUP BY continent, etat ORDER BY REPLACE(etat, '\"', '')");
-        return cursor.fetchall()
-
-    def list_continents(self):
-        "Return a list of all continents"
-        
-        from django.db import connection
-        cursor = connection.cursor()
-
-        cursor.execute("SELECT DISTINCT(name) FROM locations WHERE type = 'continent' ORDER BY name")
-        result_set = cursor.fetchall()
-        result = []
-        for a, in result_set:
-            if a != '' and a != 'N': # CREM fix
-                result.append(a)
+                stat[continent][country] = count
+                
+        keys1 = stat.keys()
+        keys1.sort(self.__name_cmp)
+        ordered = []
+        for c in keys1:
+            keys2 = stat[c].keys()
+            keys2.sort(self.__name_cmp)
+            sub = [{'location': d, 'count': stat[c][d]} for d in keys2]
+            ordered.append({'location': c, 'countries': sub})
         
-        return result
+        return ordered
 
 
 class MediaItemQuerySet(CoreQuerySet):
@@ -269,6 +245,12 @@ class MediaItemQuerySet(CoreQuerySet):
     def by_change_time(self, from_time = None, until_time = None):
         "Find items by last change time"  
         return self._by_change_time('item', from_time, until_time)
+
+    def by_location(self, location):
+        "Find items by location"
+        from telemeta.models import LocationRelation
+        descendants = LocationRelation.objects.filter(ancestor_location=location)
+        return self.filter(Q(location=location) | Q(location__in=descendants))
             
 class MediaItemManager(CoreManager):
     "Manage media items queries"
@@ -301,3 +283,42 @@ class MediaItemManager(CoreManager):
         return self.get_query_set().by_change_time(*args, **kwargs)
     by_change_time.__doc__ = MediaItemQuerySet.by_change_time.__doc__    
 
+    def by_location(self, *args, **kwargs):
+        return self.get_query_set().by_location(*args, **kwargs)
+    by_location.__doc__ = MediaItemQuerySet.by_location.__doc__    
+
+class LocationQuerySet(CoreQuerySet):
+    def by_flatname(self, flatname):
+        map = LocationManager.flatname_map()
+        return self.filter(pk=map[flatname])
+
+class LocationManager(CoreManager):
+    __flatname_map = None
+
+    def get_query_set(self):
+        "Return location query set"
+        return LocationQuerySet(self.model)
+
+    @classmethod
+    def flatname_map(cls):
+        if cls.__flatname_map:
+            return cls.__flatname_map
+
+        from telemeta.models import Location
+        map = {}
+        locations = Location.objects.filter(Q(type=Location.COUNTRY) | Q(type=Location.CONTINENT))
+        for l in locations:
+            flatname = unaccent(l.name).lower()
+            flatname = re.sub('[^a-z]', '_', flatname)
+            while map.has_key(flatname):
+                flatname = '_' + flatname
+            map[flatname] = l.id
+
+        cls.__flatname_map = map
+        return map
+            
+    def by_flatname(self, *args, **kwargs):
+        return self.get_query_set().by_flatname(*args, **kwargs)
+    by_flatname.__doc__ = LocationQuerySet.by_flatname.__doc__    
+
+    
index 6ba9e731a4fa729cd2ef62119bc0f3aec081c5aa..55bc8bfdcc85572b20f616c3456df305d560918d 100644 (file)
@@ -6,15 +6,15 @@
 {% if continents %}
 <ul class="continents">
 {% for continent in continents %}
-  <li class="name"><b><a href="{% url telemeta-geo-countries continent.flatname %}">{{ continent.name }}</a></b>
+  <li class="name"><b><a href="{% url telemeta-geo-countries continent.location.flatname %}">{{ continent.location }}</a></b>
     <ul>
     {% for country in continent.countries|slice:":10" %}
       <li class="country_name">
-        <a href="{% url telemeta-geo-country-collections continent.flatname,country.flatname %}">
-          {{ country.name|lower|capfirst }}</a></li>
+        <a href="{% url telemeta-geo-country-collections continent.location.flatname,country.location.flatname %}">
+          {{ country.location }}</a></li>
     {% endfor %}
     {% if continent.countries.10 %}
-    <li><a href="{% url telemeta-geo-countries continent.flatname %}">More..</a></li>
+    <li><a href="{% url telemeta-geo-countries continent.location.flatname %}">More..</a></li>
     {% endif %}
     </ul>
   </li>
index 6b1bba87a7f76b158c98a3d7b9edc6dc650e15c9..d244400ae40b1d422fd26a603a1bb9a76ecf39f4 100644 (file)
@@ -4,11 +4,11 @@
 
 {% block content %}
 <h3><a href="{% url telemeta-geo-continents %}">{% trans "World" %}</a> /
-  {{ continent.name }}</h3>
+  {{ continent.location.name }}</h3>
 <ul>
 {% for country in continent.countries %}
-  <li><a href="{% url telemeta-geo-country-collections continent.flatname,country.flatname %}">
-    {{ country.name|lower|capfirst }} ({{ country.count }})</a></li>
+  <li><a href="{% url telemeta-geo-country-collections continent.location.flatname,country.location.flatname %}">
+    {{ country.location.name }} ({{ country.count }})</a></li>
 {% endfor %}
 </ul>
 {% endblock %}
index c277105aa7409178dbd872884c27f1e34799e58c..3eb96d175db3eef93d3c9b2c739b15e2a54384f1 100644 (file)
@@ -4,7 +4,7 @@
 
 {% block content %}
 <h3><a href="{% url telemeta-geo-continents %}">{% trans "World" %}</a> /
-  <a href="{% url telemeta-geo-countries continent_flatname %}">{{ continent }}</a> 
+  <a href="{% url telemeta-geo-countries continent.flatname %}">{{ continent }}</a> 
   / {{ country }}</h3>
 
 {% with object_list as collections %}
index 6e057e8de8d4bcd306191a7ae192fa660cd8c0a2..9049d7233a7b6eeccc3abdd8ca253b6fa4a035fa 100644 (file)
@@ -45,8 +45,7 @@ from django.views.generic import list_detail
 from django.conf import settings
 
 import telemeta
-from telemeta.models import MediaItem
-from telemeta.models import MediaCollection
+from telemeta.models import MediaItem, Location, MediaCollection
 from telemeta.core import Component, ExtensionPoint
 from telemeta.export import *
 from telemeta.visualization import *
@@ -337,27 +336,8 @@ class WebView(Component):
         context = Context({'item': item, 'host': request.META['HTTP_HOST']})
         return HttpResponse(template.render(context), mimetype=mimetype)
 
-    def make_continents_flatnames(self, continents):
-        map = {}
-        for c in continents:
-            flat = unaccent(c['name']).lower()
-            flat = re.sub('[^a-z]', '_', flat)
-            while map.has_key(flat):
-                flat = '_' + flat
-            c['flatname'] = flat
-            map[flat] = c['name']
-            for d in c['countries']:
-                flat = unaccent(d['name']).lower()
-                flat = re.sub('[^a-z]', '_', flat)
-                while map.has_key(flat):
-                    flat = '_' + flat
-                d['flatname'] = flat
-                map[flat] = d['name']
-        return map
-
     def list_continents(self, request):
         continents = MediaCollection.objects.stat_continents()
-        self.make_continents_flatnames(continents)
         return render_to_response('telemeta/geo_continents.html', 
                     {'continents': continents })
 
@@ -367,23 +347,18 @@ class WebView(Component):
                     {'countries': countries})
 
     def list_countries(self, request, continent):                    
-        continents = MediaCollection.objects.stat_continents()
-        self.make_continents_flatnames(continents)
-        for c in continents:
-            if c["flatname"] == continent:
-                break
-        if c["flatname"] != continent:
-            raise Http404
+        continent = Location.objects.by_flatname(continent)[0]
+        data = MediaCollection.objects.stat_continents(only_continent=continent)
 
-        return render_to_response('telemeta/geo_countries.html', {'continent': c })
+        return render_to_response('telemeta/geo_countries.html', {'continent': data[0] })
 
     def list_country_collections(self, request, continent, country):
-        continents = MediaCollection.objects.stat_continents()
-        map = self.make_continents_flatnames(continents)
-        objects = MediaCollection.objects.by_country(map[country])
+        continent = Location.objects.by_flatname(continent)[0]
+        country = Location.objects.by_flatname(country)[0]
+        objects = MediaCollection.objects.by_location(country)
         return list_detail.object_list(request, objects, 
             template_name='telemeta/geo_country_collections.html', paginate_by=20,
-            extra_context={'country': map[country], 'continent_flatname': continent, 'continent': map[continent]})
+            extra_context={'country': country, 'continent': continent})
 
     def handle_oai_request(self, request):
         url         = 'http://' + request.META['HTTP_HOST'] + request.path