Rafał Pitoń 11 лет назад
Родитель
Сommit
311402fdfb

+ 14 - 3
misago/acl/panels.py

@@ -3,6 +3,11 @@ from django.template.loader import render_to_string
 from django.utils.translation import ugettext_lazy as _
 
 
+class MockUser(object):
+    def is_authenticated(self):
+        return False
+
+
 class MisagoACLPanel(Panel):
     """
     Panel that displays current user's ACL
@@ -20,12 +25,18 @@ class MisagoACLPanel(Panel):
             return _("Anonymous user")
 
     def process_response(self, request, response):
-        if hasattr(request.user, 'acl'):
-            misago_acl = request.user.acl
+        if hasattr(request, 'user'):
+            if  hasattr(request.user, 'acl'):
+                misago_user = request.user
+                misago_acl = request.user.acl
+            else:
+                misago_user = MockUser()
+                misago_acl = {}
         else:
+            misago_user = MockUser()
             misago_acl = {}
 
         self.record_stats({
-            'misago_user': request.user,
+            'misago_user': misago_user,
             'misago_acl': misago_acl,
         })

+ 7 - 0
misago/conf/defaults.py

@@ -122,6 +122,7 @@ INSTALLED_APPS = (
 )
 
 MIDDLEWARE_CLASSES = (
+    'misago.users.middleware.AvatarServerMiddleware',
     'misago.users.middleware.RealIPMiddleware',
     'django.contrib.sessions.middleware.SessionMiddleware',
     'django.middleware.common.CommonMiddleware',
@@ -231,6 +232,12 @@ MISAGO_DYNAMIC_AVATAR_DRAWER = 'misago.users.avatars.dynamic.draw_default'
 MISAGO_AVATARS_SIZES = (400, 200, 150, 100, 64, 50, 30, 20)
 
 
+# Path to avatar server
+# This path is used to detect avatar requests, which bypass most of
+# Request/response processing for performance reasons
+MISAGO_AVATAR_SERVER_PATH = '/user-avatar'
+
+
 # X-Sendfile
 # X-Sendfile is feature provided by Http servers that allows web apps to
 # delegate serving files over to the better performing server instead of

+ 28 - 8
misago/users/avatars/store.py

@@ -1,3 +1,4 @@
+from hashlib import md5
 import os
 
 from path import path
@@ -19,8 +20,11 @@ def store_avatar(user, image):
 
     normalize_image(image)
     for size in sorted(settings.MISAGO_AVATARS_SIZES, reverse=True):
+        avatar_file = '%s_%s.png' % (user.pk, size)
+        avatar_file = path(os.path.join(avatars_dir, avatar_file))
+
         image = image.resize((size, size), Image.ANTIALIAS)
-        image.save('%s/%s_%s.png' % (avatars_dir, user.pk, size), "PNG")
+        image.save(avatar_file, "PNG")
 
 
 def delete_avatar(user):
@@ -28,15 +32,18 @@ def delete_avatar(user):
     suffixes_to_delete = settings.MISAGO_AVATARS_SIZES + ('org', 'tmp')
 
     for size in suffixes_to_delete:
-        avatar_file = path('%s/%s_%s.png' % (avatars_dir, user.pk, size))
+        avatar_file = '%s_%s.png' % (user.pk, size)
+        avatar_file = path(os.path.join(avatars_dir, avatar_file))
         if avatar_file.exists():
             avatar_file.remove()
 
 
 def store_temporary_avatar(user, image):
     avatars_dir = get_existing_avatars_dir(user)
+    avatar_file = '%s_tmp.png' % user.pk
+
     normalize_image(image)
-    image.save('%s/%s_tmp.png' % (avatars_dir, user.pk), "PNG")
+    image.save(os.path.join(avatars_dir, avatar_file), "PNG")
 
 
 def store_original_avatar(user):
@@ -48,7 +55,8 @@ def store_original_avatar(user):
 
 def avatar_file_path(user, size):
     avatars_dir = get_existing_avatars_dir(user)
-    return path('%s/%s_%s.png' % (avatars_dir, user.pk, size))
+    avatar_file = '%s_%s.png' % (user.pk, size)
+    return path(os.path.join(avatars_dir, avatar_file))
 
 
 def avatar_file_exists(user, size):
@@ -63,10 +71,22 @@ def store_new_avatar(user, image):
     store_avatar(user, image)
 
 
-def get_existing_avatars_dir(user):
-    date_dir = unicode(user.joined_on.strftime('%y%m'))
-    avatars_dir = path(os.path.join(AVATARS_STORE, date_dir))
+def get_avatars_dir_path(user=None):
+    if user:
+        try:
+            user_id = user.pk
+        except AttributeError:
+            user_id = user
+
+        dir_hash = md5(str(user_id)).hexdigest()
+        hash_path = [dir_hash[0], dir_hash[1], dir_hash[2:10]]
+        return path(os.path.join(AVATARS_STORE, *hash_path))
+    else:
+        return path(os.path.join(AVATARS_STORE, 'blank'))
+
 
+def get_existing_avatars_dir(user=None):
+    avatars_dir = get_avatars_dir_path(user)
     if not avatars_dir.exists():
-        avatars_dir.mkdir()
+        avatars_dir.makedirs()
     return avatars_dir

+ 10 - 0
misago/users/middleware.py

@@ -1,8 +1,11 @@
+from django.conf import settings
 from django.contrib.auth import logout
+from django.core.urlresolvers import resolve
 from django.utils import timezone
 
 from misago.users.bans import get_request_ip_ban, get_user_ban
 from misago.users.models import AnonymousUser, Online
+from misago.users.views import avatarserver
 
 
 class RealIPMiddleware(object):
@@ -14,6 +17,13 @@ class RealIPMiddleware(object):
             request._misago_real_ip = request.META.get('REMOTE_ADDR')
 
 
+class AvatarServerMiddleware(object):
+    def process_request(self, request):
+        if request.path.startswith(settings.MISAGO_AVATAR_SERVER_PATH):
+            resolved_path = resolve(request.path)
+            return resolved_path.func(request, **resolved_path.kwargs)
+
+
 class UserMiddleware(object):
     def process_request(self, request):
         if request.user.is_anonymous():

+ 9 - 6
misago/users/urls.py

@@ -1,4 +1,5 @@
-from django.conf.urls import patterns, url
+from django.conf import settings
+from django.conf.urls import include, patterns, url
 
 
 urlpatterns = patterns('misago.users.views.auth',
@@ -59,9 +60,11 @@ urlpatterns += patterns('misago.users.views.profile',
 )
 
 
-urlpatterns += patterns('misago.users.views.avatarserver',
-    url(r'^user-avatar/(?P<size>\d+)/(?P<user_id>\d+)\.png$', 'serve_user_avatar', name="user_avatar"),
-    url(r'^user-avatar/tmp:(?P<token>[a-zA-Z0-9]+)/(?P<user_id>\d+)\.png$', 'serve_user_avatar_source', name="user_avatar_tmp", kwargs={'type': 'tmp'}),
-    url(r'^user-avatar/org:(?P<token>[a-zA-Z0-9]+)/(?P<user_id>\d+)\.png$', 'serve_user_avatar_source', name="user_avatar_org", kwargs={'type': 'org'}),
-    url(r'^user-avatar/(?P<size>\d+)\.png$', 'serve_blank_avatar', name="blank_avatar"),
+urlpatterns += patterns('',
+    url(r'^%s/' % settings.MISAGO_AVATAR_SERVER_PATH[1:], include(patterns('misago.users.views.avatarserver',
+        url(r'(?P<size>\d+)/(?P<user_id>\d+)\.png$', 'serve_user_avatar', name="user_avatar"),
+        url(r'tmp:(?P<token>[a-zA-Z0-9]+)/(?P<user_id>\d+)\.png$', 'serve_user_avatar_source', name="user_avatar_tmp", kwargs={'type': 'tmp'}),
+        url(r'org:(?P<token>[a-zA-Z0-9]+)/(?P<user_id>\d+)\.png$', 'serve_user_avatar_source', name="user_avatar_org", kwargs={'type': 'org'}),
+        url(r'(?P<size>\d+)\.png$', 'serve_blank_avatar', name="blank_avatar"),
+    )))
 )

+ 33 - 24
misago/users/views/avatarserver.py

@@ -1,27 +1,39 @@
+import os
+
+from path import path
+
 from django.conf import settings
 from django.contrib.auth import get_user_model
 
 from misago.core.fileserver import make_file_response
 
-from misago.users.avatars import set_default_avatar
+from misago.users.avatars import store
 from misago.users.avatars.uploaded import avatar_source_token
 
 
+def serve_blank_avatar(request, size):
+    size = clean_size(size)
+    avatar_dir = store.get_avatars_dir_path()
+    avatar_file = get_blank_avatar_file(size)
+    avatar_path = os.path.join(avatar_dir, avatar_file)
+    return make_file_response(avatar_path, 'image/png')
+
+
 def serve_user_avatar(request, user_id, size):
     size = clean_size(size)
-    User = get_user_model()
 
-    if user_id > 0:
-        try:
-            user = User.objects.get(id=user_id)
-            avatar_file = get_user_avatar_file(user, size)
-        except User.DoesNotExist:
-            avatar_file = get_blank_avatar_file(size)
-    else:
-        avatar_file = get_blank_avatar_file(size)
+    if int(user_id) > 0:
+        avatar_dir = store.get_avatars_dir_path(user_id)
+        avatar_file = get_user_avatar_file(user_id, size)
+        avatar_path = os.path.join(avatar_dir, avatar_file)
 
-    avatar_path = '%s/%s.png' % (settings.MISAGO_AVATAR_STORE, avatar_file)
-    return make_file_response(avatar_path, 'image/png')
+        if path(avatar_path).exists():
+            avatar_path = os.path.join(avatar_dir, avatar_file)
+            return make_file_response(avatar_path, 'image/png')
+        else:
+            return serve_blank_avatar(request, size)
+    else:
+        return serve_blank_avatar(request, size)
 
 
 def serve_user_avatar_source(request, user_id, token, type):
@@ -32,7 +44,7 @@ def serve_user_avatar_source(request, user_id, token, type):
         try:
             user = User.objects.get(id=user_id)
             if token == avatar_source_token(user, type):
-                avatar_file = get_user_avatar_file(user, type)
+                avatar_file = get_user_avatar_file(user.pk, type)
             else:
                 avatar_file = fallback_avatar
         except User.DoesNotExist:
@@ -40,14 +52,12 @@ def serve_user_avatar_source(request, user_id, token, type):
     else:
         avatar_file = fallback_avatar
 
-    avatar_path = '%s/%s.png' % (settings.MISAGO_AVATAR_STORE, avatar_file)
-    return make_file_response(avatar_path, 'image/png')
-
+    if avatar_file == fallback_avatar:
+        avatar_dir = store.get_avatars_dir_path()
+    else:
+        avatar_dir = store.get_avatars_dir_path(user_id)
 
-def serve_blank_avatar(request, size):
-    size = clean_size(size)
-    avatar_file = get_blank_avatar_file(size)
-    avatar_path = '%s/%s.png' % (settings.MISAGO_AVATAR_STORE, avatar_file)
+    avatar_path = os.path.join(avatar_dir, avatar_file)
     return make_file_response(avatar_path, 'image/png')
 
 
@@ -60,10 +70,9 @@ def clean_size(size):
     return size
 
 
-def get_user_avatar_file(user, size):
-    file_formats = (user.joined_on.strftime('%y%m'), user.pk, size)
-    return '%s/%s_%s' % file_formats
+def get_user_avatar_file(user_id, size):
+    return '%s_%s.png' % (user_id, size)
 
 
 def get_blank_avatar_file(size):
-    return 'blank/blank_%s' % size
+    return 'blank_%s.png' % size