]> git.ipfire.org Git - ipfire.org.git/commitdiff
users: Move avatar handler
authorMichael Tremer <michael.tremer@ipfire.org>
Sat, 24 Jun 2023 19:31:10 +0000 (19:31 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Sat, 24 Jun 2023 19:31:10 +0000 (19:31 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/web/__init__.py
src/web/people.py
src/web/users.py

index 9372efde4cccc3091dca1003491b2a4bb77c48dc..9c0480aa9576340c8f5b3976a7467d7b60416553 100644 (file)
@@ -152,7 +152,8 @@ class Application(tornado.web.Application):
 
                        # Users
                        (r"/users", users.IndexHandler),
-                       (r"/users/([\w]+)", users.ShowHandler),
+                       (r"/users/([a-z_][a-z0-9_-]{0,31})", users.ShowHandler),
+                       (r"/users/([a-z_][a-z0-9_-]{0,31})\.jpg", users.AvatarHandler),
 
                        # RSS feed
                        (r"/news.rss", tornado.web.RedirectHandler, { "url" : "https://blog.ipfire.org/feed.xml" }),
@@ -304,7 +305,6 @@ class Application(tornado.web.Application):
                        (r"/groups/([a-z_][a-z0-9_-]{0,31})", people.GroupHandler),
                        (r"/register", auth.RegisterHandler),
                        (r"/search", people.SearchHandler),
-                       (r"/users/([a-z_][a-z0-9_-]{0,31})\.jpg", people.AvatarHandler),
                        (r"/users/([a-z_][a-z0-9_-]{0,31})/calls/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})", people.CallHandler),
                        (r"/users/([a-z_][a-z0-9_-]{0,31})/calls(?:/(\d{4}-\d{2}-\d{2}))?", people.CallsHandler),
                        (r"/users/([a-z_][a-z0-9_-]{0,31})/edit", people.UserEditHandler),
index 1124dda7a67727dbaadeee36a58df96787add476..077240a769ccd3c1880d23fac71fbc5605261d7c 100644 (file)
@@ -1,12 +1,7 @@
 #!/usr/bin/python
 
-import PIL
 import datetime
-import imghdr
-import io
 import ldap
-import logging
-import os.path
 import tornado.web
 import urllib.parse
 
@@ -16,112 +11,12 @@ from . import auth
 from . import base
 from . import ui_modules
 
-COLOUR_LIGHT = (237,232,232)
-COLOUR_DARK  = (49,53,60)
-
 class IndexHandler(auth.CacheMixin, base.BaseHandler):
        @tornado.web.authenticated
        def get(self):
                self.render("people/index.html")
 
 
-class AvatarHandler(base.BaseHandler):
-       def get(self, uid):
-               # Get the desired size of the avatar file
-               size = self.get_argument("size", None)
-
-               try:
-                       size = int(size)
-               except (TypeError, ValueError):
-                       size = None
-
-               logging.debug("Querying for avatar of %s" % uid)
-
-               # Fetch user account
-               account = self.backend.accounts.get_by_uid(uid)
-               if not account:
-                       raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
-
-               # Allow downstream to cache this for a year
-               self.set_expires(31536000)
-
-               # Resize avatar
-               avatar = account.get_avatar(size)
-
-               # If there is no avatar, we serve a default image
-               if not avatar:
-                       logging.debug("No avatar uploaded for %s" % account)
-
-                       # Generate a random avatar with only one letter
-                       avatar = self._get_avatar(account, size=size)
-
-               # Guess content type
-               type = imghdr.what(None, avatar)
-
-               # Set headers about content
-               self.set_header("Content-Disposition", "inline; filename=\"%s.%s\"" % (account.uid, type))
-               self.set_header("Content-Type", "image/%s" % type)
-
-               # Deliver payload
-               self.finish(avatar)
-
-       def _get_avatar(self, account, size=None, **args):
-               letter = ("%s" % account)[0].upper()
-
-               if size is None:
-                       size = 256
-
-               # The generated avatar cannot be larger than 1024px
-               if size >= 2048:
-                       size = 2048
-
-               # Cache key
-               key = "avatar:letter:%s:%s" % (letter, size)
-
-               # Fetch avatar from the cache
-               avatar = self.memcached.get(key)
-               if not avatar:
-                       avatar = self._make_avatar(letter, size=size, **args)
-
-                       # Cache for forever
-                       self.memcached.set(key, avatar)
-
-               return avatar
-
-       def _make_avatar(self, letter, format="PNG", size=None, **args):
-               # Generate an image of the correct size
-               image = PIL.Image.new("RGBA", (size, size), COLOUR_LIGHT)
-
-               # Have a canvas
-               draw = PIL.ImageDraw.Draw(image)
-
-               # Load font
-               font = PIL.ImageFont.truetype(os.path.join(
-                       self.application.settings.get("static_path", ""),
-                       "fonts/Prompt-Bold.ttf"
-               ), size, encoding="unic")
-
-               # Determine size of the printed letter
-               w, h = font.getsize(letter)
-
-               # Mukta seems to be very broken and the height needs to be corrected
-               h //= 0.7
-
-               # Draw the letter in the center
-               draw.text(((size - w) / 2, (size - h) / 2), letter,
-                       font=font, fill=COLOUR_DARK)
-
-               with io.BytesIO() as f:
-                       # If writing out the image does not work with optimization,
-                       # we try to write it out without any optimization.
-                       try:
-                               image.save(f, format, optimize=True, **args)
-                       except:
-                               image.save(f, format, **args)
-
-                       return f.getvalue()
-
-
 class CallsHandler(auth.CacheMixin, base.BaseHandler):
        @tornado.web.authenticated
        def get(self, uid, date=None):
index 74b4d0a5a0bdaac68084bc45180ca933480f6e86..9b12e6247669bbea7c91d9da52fee0d0acb56879 100644 (file)
@@ -1,11 +1,19 @@
 #!/usr/bin/python
 
+import PIL
+import imghdr
+import io
+import logging
+import os.path
 import tornado.web
 
 from . import auth
 from . import base
 from . import ui_modules
 
+COLOUR_LIGHT = (237,232,232)
+COLOUR_DARK  = (49,53,60)
+
 class IndexHandler(auth.CacheMixin, base.BaseHandler):
        @tornado.web.authenticated
        def get(self):
@@ -31,6 +39,110 @@ class ShowHandler(auth.CacheMixin, base.BaseHandler):
                self.render("users/show.html", account=account)
 
 
+class AvatarHandler(base.BaseHandler):
+       def get(self, uid):
+               # Get the desired size of the avatar file
+               size = self.get_argument("size", None)
+
+               try:
+                       size = int(size)
+               except (TypeError, ValueError):
+                       size = None
+
+               logging.debug("Querying for avatar of %s" % uid)
+
+               # Fetch user account
+               account = self.backend.accounts.get_by_uid(uid)
+               if not account:
+                       raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
+
+               # Allow downstream to cache this for a year
+               self.set_expires(31536000)
+
+               # Resize avatar
+               avatar = account.get_avatar(size)
+
+               # If there is no avatar, we serve a default image
+               if not avatar:
+                       logging.debug("No avatar uploaded for %s" % account)
+
+                       # Generate a random avatar with only one letter
+                       avatar = self._get_avatar(account, size=size)
+
+               # Guess content type
+               type = imghdr.what(None, avatar)
+
+               # If we could not guess the type, we will try something else
+               if not type:
+                       # Could this be an SVG file?
+                       if avatar.startswith(b"<"):
+                               type = "svg+xml"
+
+               # Set headers about content
+               self.set_header("Content-Disposition", "inline; filename=\"%s.%s\"" % (account.uid, type))
+               if type:
+                       self.set_header("Content-Type", "image/%s" % type)
+
+               # Deliver payload
+               self.finish(avatar)
+
+       def _get_avatar(self, account, size=None, **args):
+               letter = ("%s" % account)[0].upper()
+
+               if size is None:
+                       size = 256
+
+               # The generated avatar cannot be larger than 1024px
+               if size >= 2048:
+                       size = 2048
+
+               # Cache key
+               key = "avatar:letter:%s:%s" % (letter, size)
+
+               # Fetch avatar from the cache
+               avatar = self.memcached.get(key)
+               if not avatar:
+                       avatar = self._make_avatar(letter, size=size, **args)
+
+                       # Cache for forever
+                       self.memcached.set(key, avatar)
+
+               return avatar
+
+       def _make_avatar(self, letter, format="PNG", size=None, **args):
+               # Generate an image of the correct size
+               image = PIL.Image.new("RGBA", (size, size), COLOUR_LIGHT)
+
+               # Have a canvas
+               draw = PIL.ImageDraw.Draw(image)
+
+               # Load font
+               font = PIL.ImageFont.truetype(os.path.join(
+                       self.application.settings.get("static_path", ""),
+                       "fonts/Prompt-Bold.ttf"
+               ), size, encoding="unic")
+
+               # Determine size of the printed letter
+               w, h = font.getsize(letter)
+
+               # Mukta seems to be very broken and the height needs to be corrected
+               h //= 0.7
+
+               # Draw the letter in the center
+               draw.text(((size - w) / 2, (size - h) / 2), letter,
+                       font=font, fill=COLOUR_DARK)
+
+               with io.BytesIO() as f:
+                       # If writing out the image does not work with optimization,
+                       # we try to write it out without any optimization.
+                       try:
+                               image.save(f, format, optimize=True, **args)
+                       except:
+                               image.save(f, format, **args)
+
+                       return f.getvalue()
+
+
 class ListModule(ui_modules.UIModule):
        def render(self, accounts, show_created_at=False):
                return self.render_string("users/modules/list.html", accounts=accounts,