changeset 266:4532ed27bed8

Fixing #112. Rework member map to untangle user profile and avatar cache from the membermap (since it wasn't really working anyway).
author Brian Neal <bgneal@gmail.com>
date Sat, 25 Sep 2010 18:04:44 +0000 (2010-09-25)
parents 1ba2c6bf6eb7
children ded03f2513e9
files gpp/membermap/__init__.py gpp/membermap/admin.py gpp/membermap/models.py gpp/membermap/signals.py gpp/membermap/views.py gpp/templates/membermap/balloon.html
diffstat 6 files changed, 80 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- a/gpp/membermap/__init__.py	Fri Sep 24 02:12:09 2010 +0000
+++ b/gpp/membermap/__init__.py	Sat Sep 25 18:04:44 2010 +0000
@@ -1,1 +0,0 @@
-import membermap.signals
--- a/gpp/membermap/admin.py	Fri Sep 24 02:12:09 2010 +0000
+++ b/gpp/membermap/admin.py	Sat Sep 25 18:04:44 2010 +0000
@@ -7,7 +7,7 @@
 from membermap.models import MapEntry
 
 class MapEntryAdmin(admin.ModelAdmin):
-   exclude = ('json', )
+   exclude = ('html', )
    list_display = ('user', 'location', 'lat', 'lon', 'date_updated')
    list_filter = ('date_updated', )
    date_hierarchy = 'date_updated'
--- a/gpp/membermap/models.py	Fri Sep 24 02:12:09 2010 +0000
+++ b/gpp/membermap/models.py	Sat Sep 25 18:04:44 2010 +0000
@@ -1,11 +1,9 @@
 """
 Models for the member map application.
 """
+import datetime
 from django.db import models
 from django.contrib.auth.models import User
-from django.template.loader import render_to_string
-from django.template.defaultfilters import escapejs
-import django.utils.simplejson as json
 
 from core.markup import site_markup
 
@@ -17,25 +15,18 @@
     lat = models.FloatField()
     lon = models.FloatField()
     message = models.TextField(blank=True)
-    json = models.TextField(blank=True)
-    date_updated = models.DateTimeField(auto_now_add=True)
+    html = models.TextField(blank=True)
+    date_updated = models.DateTimeField()
 
     def __unicode__(self):
-        return u'Entry for %s' % self.user.username
+        return u'Map entry for %s' % self.user.username
 
     class Meta:
         ordering = ('-date_updated', )
         verbose_name_plural = 'map entries'
 
     def save(self, *args, **kwargs):
-        msg = render_to_string('membermap/markdown.html', {
-            'user': self.user,
-            'msg': site_markup(self.message)}).strip()
-
-        self.json = json.dumps({'name': self.user.username,
-            'lat': '%10.6f' % self.lat,
-            'lon': '%10.6f' % self.lon,
-            'message': msg,
-            })
+        self.html = site_markup(self.message)
+        self.date_updated = datetime.datetime.now()
         super(MapEntry, self).save(*args, **kwargs)
 
--- a/gpp/membermap/signals.py	Fri Sep 24 02:12:09 2010 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-"""
-Signal handlers for the membermap application.
-We want to detect changes to the UserProfile model. If that person is on
-the map, re-save her MapEntry so that any avatar changes get picked up.
-"""
-from django.db.models.signals import post_save
-from bio.models import UserProfile
-from membermap.models import MapEntry
-
-
-def on_profile_save(sender, **kwargs):
-    if 'instance' in kwargs:
-        profile = kwargs['instance']
-        try:
-            map_entry = MapEntry.objects.get(user=profile.user)
-        except MapEntry.DoesNotExist:
-            # Not on the map, no harm, no foul
-            return
-        if map_entry is not None:
-            map_entry.save()
-
-
-post_save.connect(on_profile_save,
-        sender=UserProfile,
-        dispatch_uid='membermap.signals')
--- a/gpp/membermap/views.py	Fri Sep 24 02:12:09 2010 +0000
+++ b/gpp/membermap/views.py	Sat Sep 25 18:04:44 2010 +0000
@@ -2,14 +2,21 @@
 Views for the membermap application.
 """
 from django.shortcuts import render_to_response
+from django.template.loader import render_to_string
 from django.template import RequestContext
 from django.http import HttpResponse
 from django.http import HttpResponseBadRequest
 from django.http import HttpResponseForbidden
 from django.views.decorators.http import require_POST
+import django.utils.simplejson as json
+from django.core.cache import cache
 
 from membermap.models import MapEntry
 from membermap.forms import MapEntryForm
+from bio.models import UserProfile
+
+CACHE_KEY = 'membermap_json'
+CACHE_TIMEOUT = 5 * 60
 
 
 def index(request):
@@ -39,16 +46,51 @@
         "users" : array of user objects
         "recent" : array of usernames recently modified
     """
-    if request.user.is_authenticated():
-        qs = MapEntry.objects.values_list('json', flat=True).order_by('user__username')
-        s = '{"users":[' + ','.join(qs) + '], "recent":['
+    if not request.user.is_authenticated():
+        return HttpResponseForbidden('You must be logged in.')
 
-        names = MapEntry.objects.values_list('user__username', flat=True)[:10]
-        s += ','.join(['"%s"' % name for name in names])
-        s += ']}'
+    # Do we have all the JSON cached?
+    s = cache.get(CACHE_KEY)
+    if s:
         return HttpResponse(s, content_type='application/json')
 
-    return HttpResponseForbidden('You must be logged in.')
+    # Compute JSON for the map
+    entries = MapEntry.objects.all().select_related().order_by('user__username')
+    users = []
+    user_ids = []
+    recent = []
+    for entry in entries.iterator():
+        users.append(dict(name=entry.user.username,
+            lat=entry.lat,
+            lon=entry.lon,
+            message=entry.html,
+            ))
+        user_ids.append(entry.user.id)
+        recent.append((entry.date_updated, entry.user.username))
+
+    # Get avatars for all users
+    profiles = UserProfile.objects.filter(user__in=user_ids).select_related()
+    avatars = {}
+    for profile in profiles.iterator():
+        if profile.avatar and profile.avatar.url:
+            avatars[profile.user.username] = profile.avatar.url
+
+    # Render the messages that go in the balloons
+    for user in users:
+        user['message'] = render_to_string('membermap/balloon.html',
+                dict(user=user, avatar_url=avatars.get(user['name'])))
+
+    # Produce the list of recent updates
+    recent.sort(reverse=True)
+    del recent[10:]
+    recent = [entry[1] for entry in recent]
+
+    # Create the JSON for the map
+    result = dict(users=users, recent=recent)
+    s = json.dumps(result, ensure_ascii=False)
+
+    cache.set(CACHE_KEY, s, CACHE_TIMEOUT)
+    return HttpResponse(s, content_type='application/json')
 
 
 @require_POST
@@ -85,7 +127,23 @@
     entry.message = msg
     entry.save()
 
-    return HttpResponse(entry.json, content_type='application/json')
+    cache.delete(CACHE_KEY)
+
+    avatar_url = None
+    profile = entry.user.get_profile()
+    if profile.avatar and profile.avatar.url:
+        avatar_url = profile.avatar.url
+
+    u = dict(name=entry.user.username,
+            lat=entry.lat,
+            lon=entry.lon,
+            message=entry.html)
+
+    u['message'] = render_to_string('membermap/balloon.html',
+        dict(user=u, avatar_url=avatar_url))
+
+    result = json.dumps(u, ensure_ascii=False)
+    return HttpResponse(result, content_type='application/json')
 
 
 @require_POST
@@ -102,7 +160,6 @@
         pass
     else:
         entry.delete()
+        cache.delete(CACHE_KEY)
 
     return HttpResponse('')
-
-# vim: ts=4 sw=4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gpp/templates/membermap/balloon.html	Sat Sep 25 18:04:44 2010 +0000
@@ -0,0 +1,6 @@
+{% if avatar_url %}
+<a href="{% url bio-view_profile username=user.name %}">
+<img src="{{ avatar_url }}" alt="{{ user.name }}" style="float:left;margin-right:3px;" /></a>
+{% endif %}
+<a href="{% url bio-view_profile username=user.name %}">{{ user.name }}</a>:<br />
+{{ user.message|safe }}